scryer_prolog/
heap_print.rs

1use crate::arena::*;
2use crate::atom_table::*;
3use crate::parser::ast::*;
4use crate::parser::dashu::base::RemEuclid;
5use crate::parser::dashu::integer::Sign;
6use crate::parser::dashu::{ibig, Integer, Rational};
7use crate::{
8    alpha_numeric_char, capital_letter_char, cut_char, decimal_digit_char, graphic_token_char,
9    is_fx, is_infix, is_postfix, is_prefix, is_xf, is_xfx, is_xfy, is_yfx, semicolon_char,
10    sign_char, single_quote_char, small_letter_char, solo_char, variable_indicator_char,
11};
12
13use crate::forms::*;
14use crate::heap_iter::*;
15use crate::machine::heap::*;
16use crate::machine::machine_indices::*;
17use crate::machine::machine_state::pstr_loc_and_offset;
18use crate::machine::partial_string::*;
19use crate::machine::stack::*;
20use crate::machine::streams::*;
21use crate::types::*;
22
23use dashu::base::Signed;
24use ordered_float::OrderedFloat;
25
26use indexmap::IndexMap;
27
28use std::cell::Cell;
29use std::convert::TryFrom;
30use std::iter::once;
31use std::net::{IpAddr, TcpListener};
32use std::rc::Rc;
33use std::sync::Arc;
34
35/* contains the location, name, precision and Specifier of the parent op. */
36#[derive(Debug, Copy, Clone)]
37pub(crate) enum DirectedOp {
38    Left(Atom, OpDesc),
39    Right(Atom, OpDesc),
40}
41
42impl DirectedOp {
43    #[inline]
44    fn as_atom(&self) -> Atom {
45        match self {
46            &DirectedOp::Left(name, _) | &DirectedOp::Right(name, _) => name,
47        }
48    }
49
50    #[inline]
51    fn is_prefix(&self) -> bool {
52        match self {
53            &DirectedOp::Left(_name, cell) | &DirectedOp::Right(_name, cell) => {
54                is_prefix!(cell.get_spec() as u32)
55            }
56        }
57    }
58
59    #[inline]
60    fn is_negative_sign(&self) -> bool {
61        match self {
62            &DirectedOp::Left(name, cell) | &DirectedOp::Right(name, cell) => {
63                name == atom!("-") && is_prefix!(cell.get_spec() as u32)
64            }
65        }
66    }
67
68    #[inline]
69    fn is_left(&self) -> bool {
70        matches!(self, DirectedOp::Left(..))
71    }
72}
73
74fn needs_bracketing(child_desc: OpDesc, op: &DirectedOp) -> bool {
75    match op {
76        DirectedOp::Left(name, cell) => {
77            let (priority, spec) = cell.get();
78
79            if &*name.as_str() == "-" {
80                let child_assoc = child_desc.get_spec();
81                if is_prefix!(spec) && (is_postfix!(child_assoc) || is_infix!(child_assoc)) {
82                    return true;
83                }
84            }
85
86            let is_strict_right = is_yfx!(spec) || is_xfx!(spec) || is_fx!(spec);
87            child_desc.get_prec() > priority
88                || (child_desc.get_prec() == priority && is_strict_right)
89        }
90        DirectedOp::Right(_, cell) => {
91            let (priority, spec) = cell.get();
92            let is_strict_left = is_xfx!(spec) || is_xfy!(spec) || is_xf!(spec);
93
94            if child_desc.get_prec() > priority
95                || (child_desc.get_prec() == priority && is_strict_left)
96            {
97                true
98            } else if (is_postfix!(spec) || is_infix!(spec)) && !is_postfix!(child_desc.get_spec())
99            {
100                *cell != child_desc && child_desc.get_prec() == priority
101            } else {
102                false
103            }
104        }
105    }
106}
107
108impl<'a, ElideLists> StackfulPreOrderHeapIter<'a, ElideLists> {
109    /*
110     * descend into the subtree where the iterator is currently parked
111     * and check that the leftmost leaf is a number, with every node
112     * encountered on the way an infix or postfix operator, unblocked
113     * by brackets.
114     */
115    fn leftmost_leaf_has_property<P>(&self, op_dir: &OpDir, property_check: P) -> bool
116    where
117        P: Fn(HeapCellValue) -> bool,
118    {
119        let mut h = match self.stack_last() {
120            Some(h) => h,
121            None => return false,
122        };
123
124        let mut parent_spec = DirectedOp::Left(atom!("-"), OpDesc::build_with(200, FY as u8));
125
126        loop {
127            let cell = self.read_cell(h);
128
129            read_heap_cell!(cell,
130                (HeapCellValueTag::Str, s) => {
131                    read_heap_cell!(self.heap[s],
132                        (HeapCellValueTag::Atom, (name, _arity)) => {
133                            if let Some(spec) = fetch_atom_op_spec(name, None, op_dir) {
134                                if is_postfix!(spec.get_spec() as u32) || is_infix!(spec.get_spec() as u32) {
135                                    if needs_bracketing(spec, &parent_spec) {
136                                        return false;
137                                    } else {
138                                        h = IterStackLoc::iterable_loc(s + 1, HeapOrStackTag::Heap);
139                                        parent_spec = DirectedOp::Right(name, spec);
140                                        continue;
141                                    }
142                                }
143                            }
144
145                            return false;
146                        }
147                        _ => {
148                            return false;
149                        }
150                    )
151                }
152                _ => {
153                    return property_check(cell);
154                }
155            )
156        }
157    }
158
159    fn immediate_leaf_has_property<P>(&self, property_check: P) -> bool
160    where
161        P: Fn(HeapCellValue) -> bool,
162    {
163        let cell = match self.stack_last() {
164            Some(h) => self.read_cell(h),
165            None => return false,
166        };
167
168        property_check(cell)
169    }
170}
171
172fn char_to_string(is_quoted: bool, c: char) -> String {
173    match c {
174        '\'' if is_quoted => "\\'".to_string(),
175        '\n' if is_quoted => "\\n".to_string(),
176        '\r' if is_quoted => "\\r".to_string(),
177        '\t' if is_quoted => "\\t".to_string(),
178        '\u{0b}' if is_quoted => "\\v".to_string(), // UTF-8 vertical tab
179        '\u{0c}' if is_quoted => "\\f".to_string(), // UTF-8 form feed
180        '\u{08}' if is_quoted => "\\b".to_string(), // UTF-8 backspace
181        '\u{07}' if is_quoted => "\\a".to_string(), // UTF-8 alert
182        '\\' if is_quoted => "\\\\".to_string(),
183        ' ' | '\'' | '\n' | '\r' | '\t' | '\u{0b}' | '\u{0c}' | '\u{08}' | '\u{07}' | '"'
184        | '\\' => c.to_string(),
185        _ => {
186            if c.is_whitespace() || c.is_control() {
187                // print all other control and whitespace characters in hex.
188                format!("\\x{:x}\\", c as u32)
189            } else {
190                c.to_string()
191            }
192        }
193    }
194}
195
196#[derive(Clone, Copy, Debug)]
197enum NumberFocus {
198    Unfocused(Number),
199    Denominator(TypedArenaPtr<Rational>),
200    Numerator(TypedArenaPtr<Rational>),
201}
202
203impl NumberFocus {
204    fn is_negative(&self) -> bool {
205        match self {
206            NumberFocus::Unfocused(n) => n.is_negative(),
207            NumberFocus::Denominator(r) | NumberFocus::Numerator(r) => **r < Rational::from(0),
208        }
209    }
210}
211
212#[derive(Debug, Clone, Copy)]
213struct CommaSeparatedCharList {
214    pstr: PartialString,
215    offset: usize,
216    max_depth: usize,
217    end_cell: HeapCellValue,
218    end_h: Option<usize>,
219}
220
221#[derive(Debug, Clone)]
222enum TokenOrRedirect {
223    Atom(Atom),
224    BarAsOp,
225    Char(char),
226    Op(Atom, OpDesc),
227    NumberedVar(String),
228    CompositeRedirect(usize, DirectedOp),
229    CurlyBracketRedirect(usize),
230    FunctorRedirect(usize),
231    #[allow(unused)]
232    IpAddr(IpAddr),
233    NumberFocus(usize, NumberFocus, Option<DirectedOp>),
234    Open,
235    Close,
236    Comma,
237    RawPtr(*const ArenaHeader),
238    Space,
239    LeftCurly,
240    RightCurly,
241    ChildOpenList,
242    ChildCloseList,
243    OpenList(Rc<Cell<(bool, usize)>>),
244    CloseList(Rc<Cell<(bool, usize)>>),
245    HeadTailSeparator,
246    StackPop,
247    CommaSeparatedCharList(CommaSeparatedCharList),
248}
249
250pub(crate) fn requires_space(atom: &str, op: &str) -> bool {
251    match atom.chars().last() {
252        Some(ac) => op
253            .chars()
254            .next()
255            .map(|oc| {
256                if ac == '0' {
257                    oc == '\'' || oc == '(' || alpha_numeric_char!(oc)
258                } else if alpha_numeric_char!(ac) {
259                    oc == '(' || alpha_numeric_char!(oc)
260                } else if graphic_token_char!(ac) {
261                    graphic_token_char!(oc)
262                } else if variable_indicator_char!(ac) || capital_letter_char!(ac) {
263                    alpha_numeric_char!(oc)
264                } else if sign_char!(ac) {
265                    sign_char!(oc) || decimal_digit_char!(oc)
266                } else if single_quote_char!(ac) {
267                    single_quote_char!(oc)
268                } else {
269                    false
270                }
271            })
272            .unwrap_or(false),
273        _ => false,
274    }
275}
276
277fn non_quoted_graphic_token<Iter: Iterator<Item = char>>(mut iter: Iter, c: char) -> bool {
278    if c == '/' {
279        match iter.next() {
280            None => true,
281            Some('*') => false, // if we start with comment token, we must quote.
282            Some(c) => {
283                if graphic_token_char!(c) {
284                    iter.all(|c| graphic_token_char!(c))
285                } else {
286                    false
287                }
288            }
289        }
290    } else if c == '.' {
291        match iter.next() {
292            None => false,
293            Some(c) => {
294                if graphic_token_char!(c) {
295                    iter.all(|c| graphic_token_char!(c))
296                } else {
297                    false
298                }
299            }
300        }
301    } else {
302        iter.all(|c| graphic_token_char!(c))
303    }
304}
305
306pub(super) fn non_quoted_token<Iter: Iterator<Item = char>>(mut iter: Iter) -> bool {
307    if let Some(c) = iter.next() {
308        if small_letter_char!(c) {
309            iter.all(|c| alpha_numeric_char!(c))
310        } else if graphic_token_char!(c) {
311            non_quoted_graphic_token(iter, c)
312        } else if semicolon_char!(c) || cut_char!(c) {
313            iter.next().is_none()
314        } else if c == '[' {
315            iter.next() == Some(']') && iter.next().is_none()
316        } else if c == '{' {
317            iter.next() == Some('}') && iter.next().is_none()
318        } else if solo_char!(c) {
319            !(c == '(' || c == ')' || c == '}' || c == ']' || c == ',' || c == '%' || c == '|')
320        } else {
321            false
322        }
323    } else {
324        false
325    }
326}
327
328#[allow(clippy::len_without_is_empty)]
329pub trait HCValueOutputter {
330    type Output;
331
332    fn new() -> Self;
333    fn push_char(&mut self, c: char);
334    fn append(&mut self, s: &str);
335    fn begin_new_var(&mut self);
336    fn insert(&mut self, index: usize, c: char);
337    fn result(self) -> Self::Output;
338    fn ends_with(&self, s: &str) -> bool;
339    fn len(&self) -> usize;
340    fn truncate(&mut self, len: usize);
341    fn as_str(&self) -> &str;
342}
343
344#[derive(Debug)]
345pub struct PrinterOutputter {
346    contents: String,
347}
348
349impl HCValueOutputter for PrinterOutputter {
350    type Output = String;
351
352    fn new() -> Self {
353        PrinterOutputter {
354            contents: String::new(),
355        }
356    }
357
358    fn append(&mut self, contents: &str) {
359        if requires_space(&self.contents, contents) {
360            self.push_char(' ');
361        }
362
363        self.contents += contents;
364    }
365
366    fn push_char(&mut self, c: char) {
367        self.contents.push(c);
368    }
369
370    fn begin_new_var(&mut self) {
371        if !self.contents.is_empty() {
372            self.contents += ", ";
373        }
374    }
375
376    fn insert(&mut self, idx: usize, c: char) {
377        self.contents.insert(idx, c);
378    }
379
380    fn result(self) -> Self::Output {
381        self.contents
382    }
383
384    fn ends_with(&self, s: &str) -> bool {
385        self.contents.ends_with(s)
386    }
387
388    fn len(&self) -> usize {
389        self.contents.len()
390    }
391
392    fn truncate(&mut self, len: usize) {
393        self.contents.truncate(len);
394    }
395
396    fn as_str(&self) -> &str {
397        &self.contents
398    }
399}
400
401#[inline(always)]
402fn is_numbered_var(name: Atom, arity: usize) -> bool {
403    arity == 1 && name == atom!("$VAR")
404}
405
406#[inline]
407fn negated_op_needs_bracketing(
408    iter: &StackfulPreOrderHeapIter<ListElider>,
409    op_dir: &OpDir,
410    op: &Option<DirectedOp>,
411) -> bool {
412    if let Some(ref op) = op {
413        op.is_negative_sign()
414            && iter.leftmost_leaf_has_property(op_dir, |addr| match Number::try_from(addr) {
415                Ok(Number::Fixnum(n)) => n.get_num() > 0,
416                Ok(Number::Float(OrderedFloat(f))) => f > 0f64,
417                Ok(Number::Integer(n)) => n.is_positive(),
418                Ok(Number::Rational(n)) => n.is_positive(),
419                _ => false,
420            })
421    } else {
422        false
423    }
424}
425
426macro_rules! push_char {
427    ($self:ident, $c:expr) => {{
428        $self.outputter.push_char($c);
429        $self.last_item_idx = $self.outputter.len();
430    }};
431}
432
433macro_rules! append_str {
434    ($self:ident, $s:expr) => {{
435        $self.last_item_idx = $self.outputter.len();
436        $self.outputter.append($s);
437    }};
438}
439
440macro_rules! print_char {
441    ($self:ident, $is_quoted:expr, $c:expr) => {
442        if non_quoted_token(once($c)) {
443            let result = char_to_string(false, $c);
444
445            push_space_if_amb!($self, &result, {
446                append_str!($self, &result);
447            });
448        } else {
449            let mut result = String::new();
450
451            if $self.quoted {
452                result.push('\'');
453                result += &char_to_string($is_quoted, $c);
454                result.push('\'');
455            } else {
456                result += &char_to_string($is_quoted, $c);
457            }
458
459            push_space_if_amb!($self, &result, {
460                append_str!($self, result.as_str());
461            });
462        }
463    };
464}
465
466pub fn fmt_float(mut fl: f64) -> String {
467    if OrderedFloat(fl) == -0f64 {
468        fl = 0f64;
469    }
470
471    let mut buffer = ryu::Buffer::new();
472    let fl_str = buffer.format(fl);
473
474    /* When printing floats with zero fractional parts in scientific notation, ryu
475     * prints "{integer part}e{exponent}" without a ".0" preceding "e",
476     * which is not valid ISO Prolog syntax. Add ".0" manually in this
477     * case.
478     */
479
480    if let Some(e_index) = fl_str.find('e') {
481        if !fl_str[0..e_index].contains('.') {
482            return fl_str[0..e_index].to_string() + ".0" + &fl_str[e_index..];
483        }
484    }
485
486    fl_str.to_string()
487}
488
489#[derive(Debug)]
490pub struct HCPrinter<'a, Outputter> {
491    outputter: Outputter,
492    iter: StackfulPreOrderHeapIter<'a, ListElider>,
493    atom_tbl: Arc<AtomTable>,
494    op_dir: &'a OpDir,
495    state_stack: Vec<TokenOrRedirect>,
496    toplevel_spec: Option<DirectedOp>,
497    last_item_idx: usize,
498    parent_of_first_op: Option<(DirectedOp, usize)>,
499    pub var_names: IndexMap<HeapCellValue, VarPtr>,
500    pub numbervars_offset: Integer,
501    pub numbervars: bool,
502    pub quoted: bool,
503    pub ignore_ops: bool,
504    pub print_strings_as_strs: bool,
505    pub max_depth: usize,
506    pub double_quotes: bool,
507}
508
509macro_rules! push_space_if_amb {
510    ($self:expr, $atom:expr, $action:block) => {
511        if $self.ambiguity_check($atom) {
512            $self.outputter.push_char(' ');
513            $action;
514        } else {
515            $action;
516        }
517    };
518}
519
520pub(crate) fn numbervar(offset: &Integer, addr: HeapCellValue) -> Option<String> {
521    fn numbervar(n: Integer) -> String {
522        static CHAR_CODES: [char; 26] = [
523            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
524            'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
525        ];
526
527        let i: usize = (&n).rem_euclid(ibig!(26)).try_into().unwrap();
528        let j = n / ibig!(26);
529
530        if j.is_zero() {
531            CHAR_CODES[i].to_string()
532        } else {
533            format!("{}{}", CHAR_CODES[i], j)
534        }
535    }
536
537    match Number::try_from(addr) {
538        Ok(Number::Fixnum(n)) if n.get_num() >= 0 => {
539            Some(numbervar(offset + Integer::from(n.get_num())))
540        }
541        Ok(Number::Integer(n)) if !n.is_negative() => Some(numbervar(Integer::from(offset + &*n))),
542        _ => None,
543    }
544}
545
546impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> {
547    pub fn new(
548        heap: &'a mut Heap,
549        atom_tbl: Arc<AtomTable>,
550        stack: &'a mut Stack,
551        op_dir: &'a OpDir,
552        output: Outputter,
553        cell: HeapCellValue,
554    ) -> Self {
555        HCPrinter {
556            outputter: output,
557            iter: stackful_preorder_iter(heap, stack, cell),
558            atom_tbl,
559            op_dir,
560            state_stack: vec![],
561            toplevel_spec: None,
562            last_item_idx: 0,
563            parent_of_first_op: None,
564            numbervars: false,
565            numbervars_offset: Integer::from(0),
566            quoted: false,
567            ignore_ops: false,
568            var_names: IndexMap::new(),
569            print_strings_as_strs: false,
570            max_depth: 0,
571            double_quotes: false,
572        }
573    }
574
575    #[inline]
576    fn ambiguity_check(&self, atom: &str) -> bool {
577        let tail = &self.outputter.as_str()[self.last_item_idx..];
578
579        if atom == "," || !self.quoted || non_quoted_token(atom.chars()) {
580            requires_space(tail, atom)
581        } else {
582            requires_space(tail, "'")
583        }
584    }
585
586    fn set_parent_of_first_op(&mut self, parent_op: Option<DirectedOp>) {
587        if let Some(op) = parent_op {
588            if op.is_left() && op.is_prefix() {
589                self.parent_of_first_op = Some((op, self.last_item_idx));
590            }
591        }
592    }
593
594    fn enqueue_op(&mut self, mut max_depth: usize, name: Atom, spec: OpDesc) {
595        if is_postfix!(spec.get_spec()) {
596            if self.max_depth_exhausted(max_depth) {
597                self.iter.pop_stack();
598                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
599            } else if self.check_max_depth(&mut max_depth) {
600                self.iter.pop_stack();
601
602                self.state_stack.push(TokenOrRedirect::Op(name, spec));
603                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
604            } else {
605                let right_directed_op = DirectedOp::Right(name, spec);
606
607                self.state_stack.push(TokenOrRedirect::Op(name, spec));
608                self.state_stack.push(TokenOrRedirect::CompositeRedirect(
609                    max_depth,
610                    right_directed_op,
611                ));
612            }
613        } else if is_prefix!(spec.get_spec()) {
614            if self.max_depth_exhausted(max_depth) {
615                self.iter.pop_stack();
616                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
617                return;
618            } else if self.check_max_depth(&mut max_depth) {
619                self.iter.pop_stack();
620
621                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
622                self.state_stack.push(TokenOrRedirect::Op(name, spec));
623            } else {
624                let op = DirectedOp::Left(name, spec);
625
626                self.state_stack
627                    .push(TokenOrRedirect::CompositeRedirect(max_depth, op));
628                self.state_stack.push(TokenOrRedirect::Op(name, spec));
629            }
630        } else {
631            if let "|" = &*name.as_str() {
632                self.format_bar_separator_op(max_depth, name, spec);
633                return;
634            };
635
636            if self.max_depth_exhausted(max_depth) {
637                self.iter.pop_stack();
638                self.iter.pop_stack();
639
640                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
641            } else if self.check_max_depth(&mut max_depth) {
642                if is_xfy!(spec.get_spec()) {
643                    let left_directed_op = DirectedOp::Left(name, spec);
644
645                    self.state_stack
646                        .push(TokenOrRedirect::CompositeRedirect(0, left_directed_op));
647
648                    self.state_stack.push(TokenOrRedirect::Op(name, spec));
649                    self.state_stack.push(TokenOrRedirect::StackPop);
650                } else {
651                    // is_yfx!
652                    let right_directed_op = DirectedOp::Right(name, spec);
653
654                    self.state_stack.push(TokenOrRedirect::StackPop);
655                    self.state_stack.push(TokenOrRedirect::Op(name, spec));
656                    self.state_stack
657                        .push(TokenOrRedirect::CompositeRedirect(0, right_directed_op));
658                }
659            } else {
660                let left_directed_op = DirectedOp::Left(name, spec);
661                let right_directed_op = DirectedOp::Right(name, spec);
662
663                self.state_stack.push(TokenOrRedirect::CompositeRedirect(
664                    max_depth,
665                    left_directed_op,
666                ));
667
668                self.state_stack.push(TokenOrRedirect::Op(name, spec));
669                self.state_stack.push(TokenOrRedirect::CompositeRedirect(
670                    max_depth,
671                    right_directed_op,
672                ));
673            }
674        }
675    }
676
677    fn format_struct(&mut self, mut max_depth: usize, arity: usize, name: Atom) -> bool {
678        if self.check_max_depth(&mut max_depth) {
679            for _ in 0..arity {
680                self.iter.pop_stack();
681            }
682
683            if arity > 0 {
684                self.state_stack.push(TokenOrRedirect::Close);
685                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
686                self.state_stack.push(TokenOrRedirect::Open);
687            }
688
689            self.state_stack.push(TokenOrRedirect::Atom(name));
690
691            return false;
692        }
693
694        if arity > 0 {
695            self.state_stack.push(TokenOrRedirect::Close);
696
697            for _ in 0..arity {
698                self.state_stack
699                    .push(TokenOrRedirect::FunctorRedirect(max_depth));
700                self.state_stack.push(TokenOrRedirect::Comma);
701            }
702
703            self.state_stack.pop();
704
705            self.state_stack.push(TokenOrRedirect::Open);
706        }
707
708        self.state_stack.push(TokenOrRedirect::Atom(name));
709
710        true
711    }
712
713    fn format_bar_separator_op(&mut self, mut max_depth: usize, name: Atom, spec: OpDesc) {
714        if self.check_max_depth(&mut max_depth) {
715            self.iter.pop_stack();
716            self.iter.pop_stack();
717
718            let ellipsis_atom = atom!("...");
719
720            self.state_stack.push(TokenOrRedirect::Atom(ellipsis_atom));
721            self.state_stack.push(TokenOrRedirect::BarAsOp);
722            self.state_stack.push(TokenOrRedirect::Atom(ellipsis_atom));
723
724            return;
725        }
726
727        let left_directed_op = DirectedOp::Left(name, spec);
728        let right_directed_op = DirectedOp::Right(name, spec);
729
730        self.state_stack.push(TokenOrRedirect::CompositeRedirect(
731            max_depth,
732            left_directed_op,
733        ));
734
735        self.state_stack.push(TokenOrRedirect::BarAsOp);
736
737        self.state_stack.push(TokenOrRedirect::CompositeRedirect(
738            max_depth,
739            right_directed_op,
740        ));
741    }
742
743    fn format_curly_braces(&mut self, mut max_depth: usize) -> bool {
744        if self.check_max_depth(&mut max_depth) {
745            self.iter.pop_stack();
746
747            let ellipsis_atom = atom!("...");
748
749            self.state_stack.push(TokenOrRedirect::RightCurly);
750            self.state_stack.push(TokenOrRedirect::Atom(ellipsis_atom));
751            self.state_stack.push(TokenOrRedirect::LeftCurly);
752
753            return false;
754        }
755
756        self.state_stack.push(TokenOrRedirect::RightCurly);
757        self.state_stack
758            .push(TokenOrRedirect::CurlyBracketRedirect(max_depth));
759        self.state_stack.push(TokenOrRedirect::LeftCurly);
760
761        true
762    }
763
764    fn format_numbered_vars(&mut self) -> bool {
765        let h = self.iter.stack_last().unwrap();
766
767        let cell = self.iter.read_cell(h);
768        let cell = heap_bound_store(self.iter.heap, heap_bound_deref(self.iter.heap, cell));
769
770        // 7.10.4
771        if let Some(var) = numbervar(&self.numbervars_offset, cell) {
772            self.iter.pop_stack();
773            self.state_stack.push(TokenOrRedirect::NumberedVar(var));
774            return true;
775        }
776
777        false
778    }
779
780    fn format_clause(
781        &mut self,
782        max_depth: usize,
783        arity: usize,
784        name: Atom,
785        op_desc: Option<OpDesc>,
786    ) -> bool {
787        if self.numbervars && is_numbered_var(name, arity) && self.format_numbered_vars() {
788            return true;
789        }
790
791        let dot_atom = atom!(".");
792
793        if let Some(spec) = op_desc {
794            if dot_atom == name && is_infix!(spec.get_spec()) && !self.ignore_ops {
795                self.push_list(max_depth);
796                return true;
797            }
798
799            if !self.ignore_ops && spec.get_prec() > 0 {
800                self.enqueue_op(max_depth, name, spec);
801                return true;
802            }
803        }
804
805        match (name, arity) {
806            (atom!("{}"), 1) if !self.ignore_ops => self.format_curly_braces(max_depth),
807            _ => self.format_struct(max_depth, arity, name),
808        }
809    }
810
811    fn offset_as_string(&mut self, h: IterStackLoc) -> Option<String> {
812        let cell = self.iter.read_cell(h);
813
814        if let Some(var) = self.var_names.get(&cell) {
815            read_heap_cell!(cell,
816               (HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => {
817                   return Some(var.borrow().to_string());
818               }
819               _ => {
820                   self.iter.push_stack(h);
821                   return None;
822               }
823            );
824        }
825
826        read_heap_cell!(cell,
827            (HeapCellValueTag::Lis | HeapCellValueTag::Str, h) => {
828                Some(format!("{}", h))
829            }
830            (HeapCellValueTag::Var | HeapCellValueTag::AttrVar, h) => {
831                Some(format!("_{}", h))
832            }
833            (HeapCellValueTag::StackVar, h) => {
834                Some(format!("_s_{}", h))
835            }
836            _ => {
837                None
838            }
839        )
840    }
841
842    fn check_for_seen(&mut self, max_depth: &mut usize) -> Option<HeapCellValue> {
843        if let Some(mut orig_cell) = self.iter.next() {
844            loop {
845                let is_cyclic = orig_cell.get_forwarding_bit();
846
847                let cell =
848                    heap_bound_store(self.iter.heap, heap_bound_deref(self.iter.heap, orig_cell));
849                let cell = unmark_cell_bits!(cell);
850
851                match self.var_names.get(&cell).cloned() {
852                    Some(var) if cell.is_var() => {
853                        // If cell is an unbound variable and maps to
854                        // a name via heap_locs, append the name to
855                        // the current output, and return None. None
856                        // short-circuits handle_heap_term.
857                        // self.iter.pop_stack();
858
859                        let var_str = var.borrow().to_string();
860
861                        push_space_if_amb!(self, &var_str, {
862                            append_str!(self, &var_str);
863                        });
864
865                        return None;
866                    }
867                    var_opt => {
868                        if is_cyclic && cell.is_compound(self.iter.heap) {
869                            // self-referential variables are marked "cyclic".
870                            read_heap_cell!(cell,
871                                (HeapCellValueTag::Lis, vh) => {
872                                    if self.iter.heap[vh].get_forwarding_bit() {
873                                        self.iter.pop_stack();
874                                    }
875
876                                    if self.iter.heap[vh+1].get_forwarding_bit() {
877                                        self.iter.pop_stack();
878                                    }
879                                }
880                                _ => {}
881                            );
882
883                            match var_opt {
884                                Some(var) => {
885                                    // If the term is bound to a named variable,
886                                    // print the variable's name to output.
887                                    let var_str = var.borrow().to_string();
888
889                                    push_space_if_amb!(self, &var_str, {
890                                        append_str!(self, &var_str);
891                                    });
892                                }
893                                None => {
894                                    if self.max_depth == 0 || *max_depth == 0 {
895                                        // otherwise, contract it to an ellipsis.
896                                        push_space_if_amb!(self, "...", {
897                                            append_str!(self, "...");
898                                        });
899                                    } else {
900                                        debug_assert!(cell.is_ref());
901
902                                        // as usual, the WAM's
903                                        // optimization of the Lis tag
904                                        // (conflating the location of
905                                        // the list and that of its
906                                        // first element) needs
907                                        // special consideration here
908                                        // lest we find ourselves in
909                                        // an infinite loop.
910                                        if cell.get_tag() == HeapCellValueTag::Lis {
911                                            *max_depth -= 1;
912                                        }
913
914                                        let h = cell.get_value() as usize;
915                                        self.iter.push_stack(IterStackLoc::iterable_loc(
916                                            h,
917                                            HeapOrStackTag::Heap,
918                                        ));
919
920                                        if let Some(cell) = self.iter.next() {
921                                            orig_cell = cell;
922                                            continue;
923                                        }
924                                    }
925                                }
926                            }
927
928                            return None;
929                        }
930
931                        return Some(cell);
932                    }
933                }
934            }
935        } else {
936            while self.iter.pop_stack().is_none() {}
937            None
938        }
939    }
940
941    fn print_impromptu_atom(&mut self, atom: Atom) {
942        let result = self.print_op_addendum(&atom.as_str());
943
944        push_space_if_amb!(self, result.as_str(), {
945            append_str!(self, &result);
946        });
947    }
948
949    fn print_op_addendum(&mut self, atom: &str) -> String {
950        if !self.quoted || non_quoted_token(atom.chars()) {
951            atom.to_string()
952        } else if atom == "''" {
953            "''".to_string()
954        } else {
955            let mut result = String::new();
956
957            if self.quoted {
958                result.push('\'');
959            }
960
961            for c in atom.chars() {
962                result += &char_to_string(self.quoted, c);
963            }
964
965            if self.quoted {
966                result.push('\'');
967            }
968
969            result
970        }
971    }
972
973    fn print_op(&mut self, atom: &str) {
974        let result = if atom == "," {
975            ",".to_string()
976        } else {
977            self.print_op_addendum(atom)
978        };
979
980        push_space_if_amb!(self, &result, {
981            append_str!(self, &result);
982        });
983    }
984
985    #[inline]
986    fn print_ip_addr(&mut self, ip: IpAddr) {
987        push_char!(self, '\'');
988        append_str!(self, &format!("{}", ip));
989        push_char!(self, '\'');
990    }
991
992    #[inline]
993    fn print_raw_ptr(&mut self, ptr: *const ArenaHeader) {
994        append_str!(self, &format!("0x{:x}", ptr as *const u8 as usize));
995    }
996
997    fn print_number(&mut self, max_depth: usize, n: NumberFocus, op: &Option<DirectedOp>) {
998        let (add_brackets, op_is_prefix) = if let Some(op) = op {
999            (op.is_negative_sign() && !n.is_negative(), op.is_prefix())
1000        } else {
1001            (false, false)
1002        };
1003
1004        if add_brackets {
1005            if op_is_prefix && !self.outputter.ends_with(" ") {
1006                push_char!(self, ' ');
1007            }
1008
1009            push_char!(self, '(');
1010        }
1011
1012        match n {
1013            NumberFocus::Unfocused(n) => match n {
1014                Number::Float(OrderedFloat(fl)) => {
1015                    let output_str = fmt_float(fl);
1016
1017                    push_space_if_amb!(self, &output_str, {
1018                        append_str!(self, &output_str);
1019                    });
1020                }
1021                Number::Rational(r) => {
1022                    self.print_rational(max_depth, r, *op);
1023                }
1024                n => {
1025                    let output_str = format!("{}", n);
1026
1027                    push_space_if_amb!(self, &output_str, {
1028                        append_str!(self, &output_str);
1029                    });
1030                }
1031            },
1032            NumberFocus::Denominator(r) => {
1033                let output_str = format!("{}", r.denominator());
1034
1035                push_space_if_amb!(self, &output_str, {
1036                    append_str!(self, &output_str);
1037                });
1038            }
1039            NumberFocus::Numerator(r) => {
1040                let output_str = format!("{}", r.numerator());
1041
1042                push_space_if_amb!(self, &output_str, {
1043                    append_str!(self, &output_str);
1044                });
1045            }
1046        }
1047
1048        if add_brackets {
1049            push_char!(self, ')');
1050        }
1051    }
1052
1053    fn print_rational(
1054        &mut self,
1055        mut max_depth: usize,
1056        r: TypedArenaPtr<Rational>,
1057        parent_op: Option<DirectedOp>,
1058    ) {
1059        if self.check_max_depth(&mut max_depth) {
1060            self.state_stack.push(TokenOrRedirect::Close);
1061            self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1062            self.state_stack.push(TokenOrRedirect::Open);
1063
1064            self.state_stack.push(TokenOrRedirect::Atom(atom!("rdiv")));
1065
1066            return;
1067        }
1068
1069        match self.op_dir.get(&(atom!("rdiv"), Fixity::In)) {
1070            Some(op_desc) => {
1071                if r.is_int() {
1072                    let output_str = format!("{}", r);
1073
1074                    push_space_if_amb!(self, &output_str, {
1075                        append_str!(self, &output_str);
1076                    });
1077
1078                    return;
1079                }
1080
1081                let rdiv_ct = atom!("rdiv");
1082
1083                let left_directed_op = if op_desc.get_prec() > 0 {
1084                    Some(DirectedOp::Left(rdiv_ct, *op_desc))
1085                } else {
1086                    None
1087                };
1088
1089                let right_directed_op = if op_desc.get_prec() > 0 {
1090                    Some(DirectedOp::Right(rdiv_ct, *op_desc))
1091                } else {
1092                    None
1093                };
1094
1095                if !self.ignore_ops && op_desc.get_prec() > 0 {
1096                    self.state_stack.push(TokenOrRedirect::NumberFocus(
1097                        max_depth,
1098                        NumberFocus::Denominator(r),
1099                        left_directed_op,
1100                    ));
1101                    self.state_stack
1102                        .push(TokenOrRedirect::Op(rdiv_ct, *op_desc));
1103                    self.state_stack.push(TokenOrRedirect::NumberFocus(
1104                        max_depth,
1105                        NumberFocus::Numerator(r),
1106                        right_directed_op,
1107                    ));
1108
1109                    self.set_parent_of_first_op(parent_op);
1110                } else {
1111                    self.state_stack.push(TokenOrRedirect::Close);
1112
1113                    self.state_stack.push(TokenOrRedirect::NumberFocus(
1114                        max_depth,
1115                        NumberFocus::Denominator(r),
1116                        None,
1117                    ));
1118
1119                    self.state_stack.push(TokenOrRedirect::Comma);
1120
1121                    self.state_stack.push(TokenOrRedirect::NumberFocus(
1122                        max_depth,
1123                        NumberFocus::Numerator(r),
1124                        None,
1125                    ));
1126
1127                    self.state_stack.push(TokenOrRedirect::Open);
1128                    self.state_stack.push(TokenOrRedirect::Atom(rdiv_ct));
1129                }
1130            }
1131            _ => {
1132                unreachable!()
1133            }
1134        }
1135    }
1136
1137    // returns true if max_depth limit is reached and ellipsis is printed.
1138    fn print_string_as_functor(&mut self, focus: usize, max_depth: &mut usize) -> bool {
1139        let iter = HeapPStrIter::new(self.iter.heap, focus);
1140
1141        for (char_count, c) in iter.chars().enumerate() {
1142            if self.check_max_depth(max_depth) {
1143                if char_count > 0 {
1144                    self.state_stack.push(TokenOrRedirect::Close);
1145                }
1146
1147                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1148                return true;
1149            }
1150
1151            append_str!(self, "'.'");
1152            push_char!(self, '(');
1153
1154            print_char!(self, self.quoted, c);
1155            push_char!(self, ',');
1156
1157            self.state_stack.push(TokenOrRedirect::Close);
1158        }
1159
1160        false
1161    }
1162
1163    // proper strings are terminal so there's no need for max_depth to
1164    // be a mutable ref here.
1165    fn print_proper_string(&mut self, focus: usize, max_depth: usize) {
1166        push_char!(self, '"');
1167
1168        let iter = HeapPStrIter::new(self.iter.heap, focus);
1169        let char_to_string = |c: char| {
1170            // refrain from quoting characters other than '"' and '\'
1171            // unless self.quoted is true.
1172            match c {
1173                '\\' => "\\\\".to_string(),
1174                '"' => "\\\"".to_string(),
1175                _ => char_to_string(self.quoted, c),
1176            }
1177        };
1178
1179        if max_depth == 0 {
1180            for c in iter.chars() {
1181                for c in char_to_string(c).chars() {
1182                    push_char!(self, c);
1183                }
1184            }
1185        } else {
1186            let mut char_count = 0;
1187
1188            for c in iter.chars().take(max_depth) {
1189                char_count += 1;
1190
1191                for c in char_to_string(c).chars() {
1192                    push_char!(self, c);
1193                }
1194            }
1195
1196            if char_count == max_depth {
1197                append_str!(self, " ...");
1198            }
1199        }
1200
1201        push_char!(self, '"');
1202    }
1203
1204    fn remove_list_children(&mut self, h: usize) {
1205        match self.iter.heap[h].get_tag() {
1206            HeapCellValueTag::Lis => {
1207                self.iter.pop_stack();
1208                self.iter.pop_stack();
1209            }
1210            HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset => {
1211                self.iter.pop_stack();
1212            }
1213            HeapCellValueTag::CStr => {}
1214            _ => {
1215                unreachable!();
1216            }
1217        }
1218    }
1219
1220    fn print_list_like(&mut self, mut max_depth: usize) {
1221        let focus = self.iter.focus();
1222        let mut heap_pstr_iter = HeapPStrIter::new(self.iter.heap, focus.value() as usize);
1223
1224        if heap_pstr_iter.next().is_some() {
1225            for _ in heap_pstr_iter.by_ref() {}
1226        } else {
1227            return self.push_list(max_depth);
1228        }
1229
1230        let end_h = heap_pstr_iter.focus();
1231        let end_cell = heap_pstr_iter.focus;
1232
1233        if self.check_max_depth(&mut max_depth) {
1234            self.remove_list_children(focus.value() as usize);
1235            self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1236            return;
1237        }
1238
1239        let at_cdr = self.outputter.ends_with("|");
1240
1241        if self.double_quotes && !self.ignore_ops && end_cell.is_string_terminator(self.iter.heap) {
1242            self.remove_list_children(focus.value() as usize);
1243            return self.print_proper_string(focus.value() as usize, max_depth);
1244        }
1245
1246        if self.ignore_ops {
1247            self.at_cdr(",");
1248            self.remove_list_children(focus.value() as usize);
1249
1250            if !self.print_string_as_functor(focus.value() as usize, &mut max_depth) {
1251                if end_cell == empty_list_as_cell!() {
1252                    if !self.at_cdr("") {
1253                        append_str!(self, "[]");
1254                    }
1255                } else {
1256                    self.state_stack
1257                        .push(TokenOrRedirect::FunctorRedirect(max_depth));
1258                    self.iter
1259                        .push_stack(IterStackLoc::iterable_loc(end_h, HeapOrStackTag::Heap));
1260                }
1261            }
1262        } else {
1263            let value = heap_bound_store(
1264                self.iter.heap,
1265                heap_bound_deref(self.iter.heap, self.iter.read_cell(focus)),
1266            );
1267
1268            read_heap_cell!(value,
1269                (HeapCellValueTag::Lis) => {
1270                    self.push_list(max_depth)
1271                }
1272                _ => {
1273                    let switch = Rc::new(Cell::new((!at_cdr, 0)));
1274                    let switch = self.close_list(switch);
1275
1276                    let (h, offset) = pstr_loc_and_offset(self.iter.heap, focus.value() as usize);
1277
1278                    let offset = offset.get_num() as usize;
1279                    let tag = value.get_tag();
1280
1281                    let end_h = if tag == HeapCellValueTag::PStrOffset {
1282                        // remove the fixnum offset from the iterator stack so we don't
1283                        // print an extraneous number. pstr offset value cells are never
1284                        // used by the iterator to mark cyclic terms so the removal is safe.
1285                        self.iter.pop_stack();
1286                        Some(end_h)
1287                    } else {
1288                        None
1289                    };
1290
1291                    if !self.max_depth_exhausted(max_depth) {
1292                        let pstr = cell_as_string!(self.iter.heap[h]);
1293                        self.state_stack.push(TokenOrRedirect::CommaSeparatedCharList(CommaSeparatedCharList {
1294                            pstr, offset, max_depth, end_cell, end_h,
1295                        }));
1296                    } else {
1297                        self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1298                    }
1299
1300                    self.open_list(switch);
1301                }
1302            );
1303        }
1304    }
1305
1306    #[inline]
1307    fn max_depth_exhausted(&self, max_depth: usize) -> bool {
1308        self.max_depth > 0 && max_depth == 0
1309    }
1310
1311    fn check_max_depth(&self, max_depth: &mut usize) -> bool {
1312        if self.max_depth > 0 && *max_depth == 0 {
1313            return true;
1314        }
1315
1316        if *max_depth > 0 {
1317            *max_depth -= 1;
1318        }
1319
1320        false
1321    }
1322
1323    fn close_list(&mut self, switch: Rc<Cell<(bool, usize)>>) -> Option<Rc<Cell<(bool, usize)>>> {
1324        if let Some(TokenOrRedirect::Op(_, op_desc)) = self.state_stack.last() {
1325            if is_postfix!(op_desc.get_spec()) || is_infix!(op_desc.get_spec()) {
1326                self.state_stack.push(TokenOrRedirect::ChildCloseList);
1327                return None;
1328            }
1329        }
1330
1331        self.state_stack
1332            .push(TokenOrRedirect::CloseList(switch.clone()));
1333        Some(switch)
1334    }
1335
1336    fn open_list(&mut self, switch: Option<Rc<Cell<(bool, usize)>>>) {
1337        self.state_stack.push(match switch {
1338            Some(switch) => TokenOrRedirect::OpenList(switch),
1339            None => TokenOrRedirect::ChildOpenList,
1340        });
1341    }
1342
1343    fn push_list(&mut self, mut max_depth: usize) {
1344        if self.max_depth_exhausted(max_depth) {
1345            self.iter.pop_stack();
1346            self.iter.pop_stack();
1347
1348            self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1349
1350            return;
1351        } else if self.check_max_depth(&mut max_depth) {
1352            self.iter.pop_stack();
1353            self.iter.pop_stack();
1354
1355            let cell = Rc::new(Cell::new((true, 0)));
1356
1357            let switch = self.close_list(cell);
1358
1359            self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1360            self.open_list(switch);
1361
1362            return;
1363        }
1364
1365        let cell = Rc::new(Cell::new((true, max_depth)));
1366
1367        let switch = self.close_list(cell);
1368
1369        self.state_stack
1370            .push(TokenOrRedirect::FunctorRedirect(max_depth));
1371        self.state_stack.push(TokenOrRedirect::HeadTailSeparator); // bar
1372        self.state_stack
1373            .push(TokenOrRedirect::FunctorRedirect(max_depth + 1));
1374
1375        self.open_list(switch);
1376    }
1377
1378    #[allow(clippy::too_many_arguments)]
1379    fn handle_op_as_struct(
1380        &mut self,
1381        name: Atom,
1382        arity: usize,
1383        op: Option<DirectedOp>,
1384        is_functor_redirect: bool,
1385        op_desc: OpDesc,
1386        negated_operand: bool,
1387        max_depth: usize,
1388    ) {
1389        let add_brackets = if !self.ignore_ops {
1390            negated_operand
1391                || if let Some(ref op) = op {
1392                    if self.numbervars && arity == 1 && name == atom!("$VAR") {
1393                        !self.iter.immediate_leaf_has_property(|addr| {
1394                            match Number::try_from(addr) {
1395                                Ok(Number::Integer(n)) => (*n).sign() == Sign::Positive,
1396                                Ok(Number::Fixnum(n)) => n.get_num() >= 0,
1397                                Ok(Number::Float(f)) => f >= OrderedFloat(0f64),
1398                                Ok(Number::Rational(r)) => (*r).sign() == Sign::Positive,
1399                                _ => false,
1400                            }
1401                        }) && needs_bracketing(op_desc, op)
1402                    } else {
1403                        needs_bracketing(op_desc, op)
1404                    }
1405                } else {
1406                    is_functor_redirect && op_desc.get_prec() >= 1000
1407                }
1408        } else {
1409            false
1410        };
1411
1412        if add_brackets {
1413            self.state_stack.push(TokenOrRedirect::Close);
1414            self.format_clause(max_depth, arity, name, Some(op_desc));
1415            self.state_stack.push(TokenOrRedirect::Open);
1416
1417            if !self.outputter.ends_with(" ") {
1418                let parent_op = self
1419                    .parent_of_first_op
1420                    .and_then(|(parent_op, last_item_idx)| {
1421                        // if parent_op isn't printed to the output string
1422                        // already, then it doesn't border the present op
1423                        // and we should return None.
1424                        if self.last_item_idx == last_item_idx {
1425                            Some(parent_op)
1426                        } else {
1427                            None
1428                        }
1429                    });
1430
1431                for op in &[op, parent_op] {
1432                    if let Some(ref op) = &op {
1433                        if op.is_left()
1434                            && (op.is_prefix() || requires_space(&op.as_atom().as_str(), "("))
1435                        {
1436                            self.state_stack.push(TokenOrRedirect::Space);
1437                            return;
1438                        }
1439                    }
1440                }
1441            }
1442        } else {
1443            self.format_clause(max_depth, arity, name, Some(op_desc));
1444        }
1445    }
1446
1447    fn print_tcp_listener(&mut self, tcp_listener: &TcpListener, max_depth: usize) {
1448        let (ip, port) = if let Ok(addr) = tcp_listener.local_addr() {
1449            (addr.ip(), addr.port())
1450        } else {
1451            let disconnected_atom = atom!("$disconnected_tcp_listener");
1452            self.state_stack
1453                .push(TokenOrRedirect::Atom(disconnected_atom));
1454
1455            return;
1456        };
1457
1458        let tcp_listener_atom = atom!("$tcp_listener");
1459
1460        if self.format_struct(max_depth, 1, tcp_listener_atom) {
1461            let atom = self.state_stack.pop().unwrap();
1462
1463            self.state_stack.pop();
1464            self.state_stack.pop();
1465
1466            self.state_stack.push(TokenOrRedirect::NumberFocus(
1467                max_depth,
1468                NumberFocus::Unfocused(Number::Fixnum(Fixnum::build_with(port as i64))),
1469                None,
1470            ));
1471            self.state_stack.push(TokenOrRedirect::Comma);
1472            self.state_stack.push(TokenOrRedirect::IpAddr(ip));
1473
1474            self.state_stack.push(TokenOrRedirect::Open);
1475            self.state_stack.push(atom);
1476        }
1477    }
1478
1479    fn print_index_ptr(&mut self, index_ptr: IndexPtr, max_depth: usize) {
1480        if self.format_struct(max_depth, 1, atom!("$index_ptr")) {
1481            let atom = self.state_stack.pop().unwrap();
1482
1483            self.state_stack.pop();
1484            self.state_stack.pop();
1485
1486            let offset = if index_ptr.is_undefined() || index_ptr.is_dynamic_undefined() {
1487                TokenOrRedirect::Atom(atom!("undefined"))
1488            } else {
1489                let idx = index_ptr.p() as i64;
1490
1491                TokenOrRedirect::NumberFocus(
1492                    max_depth,
1493                    NumberFocus::Unfocused(Number::Fixnum(Fixnum::build_with(idx))),
1494                    None,
1495                )
1496            };
1497
1498            self.state_stack.push(offset);
1499            self.state_stack.push(TokenOrRedirect::Open);
1500            self.state_stack.push(atom);
1501        }
1502    }
1503
1504    fn print_stream(&mut self, stream: Stream, max_depth: usize) {
1505        if let Some(alias) = stream.options().get_alias() {
1506            self.print_impromptu_atom(alias);
1507        } else {
1508            let stream_atom = atom!("$stream");
1509
1510            if self.format_struct(max_depth, 1, stream_atom) {
1511                let atom = if stream.is_stdout() || stream.is_stdin() {
1512                    TokenOrRedirect::Atom(atom!("user"))
1513                } else {
1514                    TokenOrRedirect::RawPtr(stream.as_ptr())
1515                };
1516
1517                let stream_root = self.state_stack.pop().unwrap();
1518
1519                self.state_stack.pop();
1520                self.state_stack.pop();
1521
1522                self.state_stack.push(atom);
1523                self.state_stack.push(TokenOrRedirect::Open);
1524                self.state_stack.push(stream_root);
1525            }
1526        }
1527    }
1528
1529    fn print_comma_separated_char_list(&mut self, char_list: CommaSeparatedCharList) {
1530        let CommaSeparatedCharList {
1531            pstr,
1532            offset,
1533            max_depth,
1534            end_cell,
1535            end_h,
1536        } = char_list;
1537        let pstr_str = pstr.as_str_from(offset);
1538
1539        if let Some(c) = pstr_str.chars().next() {
1540            let offset = offset + c.len_utf8();
1541
1542            if !self.max_depth_exhausted(max_depth) {
1543                self.state_stack
1544                    .push(TokenOrRedirect::CommaSeparatedCharList(
1545                        CommaSeparatedCharList {
1546                            pstr,
1547                            offset,
1548                            max_depth: max_depth.saturating_sub(1),
1549                            end_cell,
1550                            end_h,
1551                        },
1552                    ));
1553
1554                let max_depth_allows = self.max_depth == 0 || max_depth > 1;
1555
1556                if max_depth_allows && pstr_str.chars().nth(1).is_some() {
1557                    self.state_stack.push(TokenOrRedirect::Comma);
1558                }
1559
1560                self.state_stack.push(TokenOrRedirect::Char(c));
1561            } else {
1562                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1563                self.state_stack.push(TokenOrRedirect::HeadTailSeparator);
1564            }
1565        } else if self.max_depth_exhausted(max_depth) {
1566            self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1567            self.state_stack.push(TokenOrRedirect::HeadTailSeparator);
1568        } else if end_cell != empty_list_as_cell!() {
1569            if let Some(end_h) = end_h {
1570                self.iter
1571                    .push_stack(IterStackLoc::iterable_loc(end_h, HeapOrStackTag::Heap));
1572            }
1573
1574            self.state_stack
1575                .push(TokenOrRedirect::FunctorRedirect(max_depth + 1));
1576            self.state_stack.push(TokenOrRedirect::HeadTailSeparator);
1577        }
1578    }
1579
1580    fn handle_heap_term(
1581        &mut self,
1582        op: Option<DirectedOp>,
1583        is_functor_redirect: bool,
1584        mut max_depth: usize,
1585    ) {
1586        let negated_operand = negated_op_needs_bracketing(&self.iter, self.op_dir, &op);
1587
1588        let addr = match self.check_for_seen(&mut max_depth) {
1589            Some(addr) => addr,
1590            None => return,
1591        };
1592
1593        let print_struct = |printer: &mut Self, name: Atom, arity: usize| {
1594            if name == atom!("[]") && arity == 0 {
1595                if let Some(TokenOrRedirect::CloseList(_) | TokenOrRedirect::ChildCloseList) =
1596                    printer.state_stack.last()
1597                {
1598                    if printer.at_cdr("") {
1599                        return;
1600                    }
1601                }
1602
1603                append_str!(printer, "[]");
1604            } else if arity > 0 {
1605                if let Some(spec) = fetch_op_spec(name, arity, printer.op_dir) {
1606                    printer.handle_op_as_struct(
1607                        name,
1608                        arity,
1609                        op,
1610                        is_functor_redirect,
1611                        spec,
1612                        negated_operand,
1613                        max_depth,
1614                    );
1615                } else {
1616                    push_space_if_amb!(printer, &*name.as_str(), {
1617                        printer.format_clause(max_depth, arity, name, None);
1618                    });
1619                }
1620            } else if fetch_op_spec(name, arity, printer.op_dir).is_some() {
1621                let mut result = String::new();
1622
1623                if let Some(ref op) = op {
1624                    let op_is_prefix = op.is_prefix() && op.is_left();
1625
1626                    if op_is_prefix
1627                        || printer
1628                            .outputter
1629                            .ends_with(&format!(" {}", op.as_atom().as_str()))
1630                    {
1631                        result.push(' ');
1632                    }
1633
1634                    result.push('(');
1635                }
1636
1637                result += &printer.print_op_addendum(&name.as_str());
1638
1639                if op.is_some() {
1640                    result.push(')');
1641                }
1642
1643                push_space_if_amb!(printer, &result, {
1644                    append_str!(printer, &result);
1645                });
1646            } else {
1647                push_space_if_amb!(printer, &name.as_str(), {
1648                    printer.print_impromptu_atom(name);
1649                });
1650            }
1651        };
1652
1653        if !addr.is_var()
1654            && !addr.is_compound(self.iter.heap)
1655            && self.max_depth_exhausted(max_depth)
1656        {
1657            if !(addr == atom_as_cell!(atom!("[]")) && self.at_cdr("")) {
1658                self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1659            }
1660
1661            return;
1662        }
1663
1664        read_heap_cell!(addr,
1665            (HeapCellValueTag::Atom, (name, arity)) => {
1666                print_struct(self, name, arity);
1667            }
1668            (HeapCellValueTag::Char, c) => {
1669                let name = AtomTable::build_with(&self.atom_tbl, &String::from(c));
1670                print_struct(self, name, 0);
1671            }
1672            (HeapCellValueTag::Str, s) => {
1673                let (name, arity) = cell_as_atom_cell!(self.iter.heap[s])
1674                    .get_name_and_arity();
1675
1676                if let Some(spec) = fetch_op_spec(name, arity, self.op_dir) {
1677                    self.handle_op_as_struct(
1678                        name,
1679                        arity,
1680                        op,
1681                        is_functor_redirect,
1682                        spec,
1683                        negated_operand,
1684                        max_depth,
1685                    );
1686                } else {
1687                    push_space_if_amb!(self, &*name.as_str(), {
1688                        self.format_clause(max_depth, arity, name, None);
1689                    });
1690                }
1691            }
1692            (HeapCellValueTag::Fixnum | HeapCellValueTag::CutPoint, n) => {
1693                self.print_number(max_depth, NumberFocus::Unfocused(Number::Fixnum(n)), &op);
1694            }
1695            (HeapCellValueTag::F64, f) => {
1696                self.print_number(max_depth, NumberFocus::Unfocused(Number::Float(*f)), &op);
1697            }
1698            (HeapCellValueTag::CStr | HeapCellValueTag::PStr | HeapCellValueTag::PStrOffset) => {
1699                self.print_list_like(max_depth);
1700            }
1701            (HeapCellValueTag::Lis) => {
1702                if self.ignore_ops {
1703                    let period_atom = atom!(".");
1704                    self.format_struct(max_depth, 2, period_atom);
1705                } else {
1706                    self.print_list_like(max_depth);
1707                }
1708            }
1709            (HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => {
1710                let h = self.iter.focus();
1711
1712                if let Some(offset_str) = self.offset_as_string(h) {
1713                    push_space_if_amb!(self, &offset_str, {
1714                        append_str!(self, offset_str.as_str());
1715                    })
1716                }
1717            }
1718            (HeapCellValueTag::Cons, c) => {
1719                match_untyped_arena_ptr!(c,
1720                   (ArenaHeaderTag::Integer, n) => {
1721                       self.print_number(max_depth, NumberFocus::Unfocused(Number::Integer(n)), &op);
1722                   }
1723                   (ArenaHeaderTag::Rational, r) => {
1724                       self.print_number(max_depth, NumberFocus::Unfocused(Number::Rational(r)), &op);
1725                   }
1726                   (ArenaHeaderTag::Stream, stream) => {
1727                       self.print_stream(stream, max_depth);
1728                   }
1729                   (ArenaHeaderTag::TcpListener, listener) => {
1730                       self.print_tcp_listener(&listener, max_depth);
1731                   }
1732                   (ArenaHeaderTag::Dropped, _value) => {
1733                       self.print_impromptu_atom(atom!("$dropped_value"));
1734                   }
1735                   (ArenaHeaderTag::IndexPtr, index_ptr) => {
1736                       self.print_index_ptr(*index_ptr, max_depth);
1737                   }
1738                   _ => {
1739                   }
1740               );
1741            }
1742            _ => {
1743                unreachable!()
1744            }
1745        );
1746    }
1747
1748    fn at_cdr(&mut self, tr: &str) -> bool {
1749        let len = self.outputter.len();
1750
1751        if self.outputter.ends_with("|") {
1752            self.outputter.truncate(len - "|".len());
1753            append_str!(self, tr);
1754            true
1755        } else {
1756            false
1757        }
1758    }
1759
1760    pub fn print(mut self) -> Outputter {
1761        let spec = self.toplevel_spec.take();
1762        self.handle_heap_term(spec, false, self.max_depth);
1763
1764        while let Some(loc_data) = self.state_stack.pop() {
1765            match loc_data {
1766                TokenOrRedirect::Atom(atom) => self.print_impromptu_atom(atom),
1767                TokenOrRedirect::BarAsOp => append_str!(self, "|"),
1768                TokenOrRedirect::Char(c) => print_char!(self, self.quoted, c),
1769                TokenOrRedirect::Op(atom, op) => {
1770                    self.print_op(&atom.as_str());
1771
1772                    if is_prefix!(op.get_spec()) {
1773                        self.set_parent_of_first_op(Some(DirectedOp::Left(atom, op)));
1774                    }
1775                }
1776                TokenOrRedirect::NumberedVar(num_var) => append_str!(self, &num_var),
1777                TokenOrRedirect::CompositeRedirect(max_depth, op) => {
1778                    self.handle_heap_term(Some(op), false, max_depth)
1779                }
1780                TokenOrRedirect::CurlyBracketRedirect(max_depth) => {
1781                    self.handle_heap_term(None, false, max_depth)
1782                }
1783                TokenOrRedirect::FunctorRedirect(max_depth) => {
1784                    self.handle_heap_term(None, true, max_depth)
1785                }
1786                TokenOrRedirect::Close => push_char!(self, ')'),
1787                TokenOrRedirect::IpAddr(ip) => self.print_ip_addr(ip),
1788                TokenOrRedirect::RawPtr(ptr) => self.print_raw_ptr(ptr),
1789                TokenOrRedirect::Open => push_char!(self, '('),
1790                TokenOrRedirect::ChildOpenList => {
1791                    push_char!(self, '[');
1792                }
1793                TokenOrRedirect::ChildCloseList => {
1794                    push_char!(self, ']');
1795                }
1796                TokenOrRedirect::OpenList(delimit) => {
1797                    if !self.at_cdr(",") {
1798                        push_char!(self, '[');
1799                    } else {
1800                        let (_, max_depth) = delimit.get();
1801                        delimit.set((false, max_depth));
1802                    }
1803                }
1804                TokenOrRedirect::CloseList(delimit) => {
1805                    if delimit.get().0 {
1806                        push_char!(self, ']');
1807                    }
1808                }
1809                TokenOrRedirect::HeadTailSeparator => append_str!(self, "|"),
1810                TokenOrRedirect::NumberFocus(max_depth, n, op) => {
1811                    self.print_number(max_depth, n, &op);
1812                }
1813                TokenOrRedirect::Comma => append_str!(self, ","),
1814                TokenOrRedirect::Space => push_char!(self, ' '),
1815                TokenOrRedirect::LeftCurly => push_char!(self, '{'),
1816                TokenOrRedirect::RightCurly => push_char!(self, '}'),
1817                TokenOrRedirect::StackPop => {
1818                    self.iter.pop_stack();
1819                    self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
1820                }
1821                TokenOrRedirect::CommaSeparatedCharList(char_list) => {
1822                    self.print_comma_separated_char_list(char_list);
1823                }
1824            }
1825        }
1826
1827        self.outputter
1828    }
1829}
1830
1831#[cfg(test)]
1832mod tests {
1833    use super::*;
1834
1835    use crate::machine::mock_wam::*;
1836
1837    #[test]
1838    #[cfg_attr(miri, ignore = "blocked on streams.rs UB")]
1839    fn term_printing_tests() {
1840        let mut wam = MockWAM::new();
1841
1842        let f_atom = atom!("f");
1843        let a_atom = atom!("a");
1844        let b_atom = atom!("b");
1845        let c_atom = atom!("c");
1846
1847        wam.machine_st
1848            .heap
1849            .extend(functor!(f_atom, [atom(a_atom), atom(b_atom)]));
1850
1851        {
1852            let printer = HCPrinter::new(
1853                &mut wam.machine_st.heap,
1854                Arc::clone(&wam.machine_st.atom_tbl),
1855                &mut wam.machine_st.stack,
1856                &wam.op_dir,
1857                PrinterOutputter::new(),
1858                heap_loc_as_cell!(0),
1859            );
1860
1861            let output = printer.print();
1862
1863            assert_eq!(output.result(), "f(a,b)");
1864        }
1865
1866        all_cells_unmarked(&wam.machine_st.heap);
1867
1868        wam.machine_st.heap.clear();
1869
1870        wam.machine_st.heap.extend(functor!(
1871            f_atom,
1872            [
1873                atom(a_atom),
1874                atom(b_atom),
1875                atom(a_atom),
1876                cell(str_loc_as_cell!(0))
1877            ]
1878        ));
1879
1880        {
1881            let printer = HCPrinter::new(
1882                &mut wam.machine_st.heap,
1883                Arc::clone(&wam.machine_st.atom_tbl),
1884                &mut wam.machine_st.stack,
1885                &wam.op_dir,
1886                PrinterOutputter::new(),
1887                heap_loc_as_cell!(0),
1888            );
1889
1890            let output = printer.print();
1891
1892            assert_eq!(output.result(), "f(a,b,a,...)");
1893        }
1894
1895        all_cells_unmarked(&wam.machine_st.heap);
1896
1897        wam.machine_st.heap.clear();
1898
1899        // print L = [L|L].
1900        wam.machine_st.heap.push(list_loc_as_cell!(1));
1901        wam.machine_st.heap.push(list_loc_as_cell!(1));
1902        wam.machine_st.heap.push(list_loc_as_cell!(1));
1903
1904        {
1905            let printer = HCPrinter::new(
1906                &mut wam.machine_st.heap,
1907                Arc::clone(&wam.machine_st.atom_tbl),
1908                &mut wam.machine_st.stack,
1909                &wam.op_dir,
1910                PrinterOutputter::new(),
1911                heap_loc_as_cell!(0),
1912            );
1913
1914            let output = printer.print();
1915
1916            assert_eq!(output.result(), "[...|...]");
1917
1918            let mut printer = HCPrinter::new(
1919                &mut wam.machine_st.heap,
1920                Arc::clone(&wam.machine_st.atom_tbl),
1921                &mut wam.machine_st.stack,
1922                &wam.op_dir,
1923                PrinterOutputter::new(),
1924                heap_loc_as_cell!(0),
1925            );
1926
1927            printer
1928                .var_names
1929                .insert(list_loc_as_cell!(1), VarPtr::from("L"));
1930
1931            let output = printer.print();
1932
1933            assert_eq!(output.result(), "[L|L]");
1934        }
1935
1936        all_cells_unmarked(&wam.machine_st.heap);
1937
1938        wam.machine_st.heap.clear();
1939
1940        let functor = functor!(f_atom, [atom(a_atom), atom(b_atom), atom(b_atom)]);
1941
1942        wam.machine_st.heap.push(list_loc_as_cell!(1));
1943        wam.machine_st.heap.push(str_loc_as_cell!(5));
1944        wam.machine_st.heap.push(list_loc_as_cell!(3));
1945        wam.machine_st.heap.push(str_loc_as_cell!(5));
1946        wam.machine_st.heap.push(empty_list_as_cell!());
1947
1948        wam.machine_st.heap.extend(functor);
1949
1950        {
1951            let printer = HCPrinter::new(
1952                &mut wam.machine_st.heap,
1953                Arc::clone(&wam.machine_st.atom_tbl),
1954                &mut wam.machine_st.stack,
1955                &wam.op_dir,
1956                PrinterOutputter::new(),
1957                heap_loc_as_cell!(0),
1958            );
1959
1960            let output = printer.print();
1961
1962            assert_eq!(output.result(), "[f(a,b,b),f(a,b,b)]");
1963        }
1964
1965        all_cells_unmarked(&wam.machine_st.heap);
1966
1967        wam.machine_st.heap[4] = list_loc_as_cell!(1);
1968
1969        {
1970            let printer = HCPrinter::new(
1971                &mut wam.machine_st.heap,
1972                Arc::clone(&wam.machine_st.atom_tbl),
1973                &mut wam.machine_st.stack,
1974                &wam.op_dir,
1975                PrinterOutputter::new(),
1976                heap_loc_as_cell!(0),
1977            );
1978
1979            let output = printer.print();
1980
1981            assert_eq!(output.result(), "[f(a,b,b),f(a,b,b)|...]");
1982        }
1983
1984        all_cells_unmarked(&wam.machine_st.heap);
1985
1986        {
1987            let mut printer = HCPrinter::new(
1988                &mut wam.machine_st.heap,
1989                Arc::clone(&wam.machine_st.atom_tbl),
1990                &mut wam.machine_st.stack,
1991                &wam.op_dir,
1992                PrinterOutputter::new(),
1993                heap_loc_as_cell!(0),
1994            );
1995
1996            printer
1997                .var_names
1998                .insert(list_loc_as_cell!(1), VarPtr::from("L"));
1999
2000            let output = printer.print();
2001
2002            assert_eq!(output.result(), "[f(a,b,b),f(a,b,b)|L]");
2003        }
2004
2005        all_cells_unmarked(&wam.machine_st.heap);
2006
2007        // issue #382
2008        wam.machine_st.heap.clear();
2009        wam.machine_st.heap.push(list_loc_as_cell!(1));
2010
2011        for idx in 0..3000 {
2012            wam.machine_st.heap.push(heap_loc_as_cell!(2 * idx + 1));
2013            wam.machine_st.heap.push(list_loc_as_cell!(2 * idx + 2 + 1));
2014        }
2015
2016        wam.machine_st.heap.push(empty_list_as_cell!());
2017
2018        {
2019            let mut printer = HCPrinter::new(
2020                &mut wam.machine_st.heap,
2021                Arc::clone(&wam.machine_st.atom_tbl),
2022                &mut wam.machine_st.stack,
2023                &wam.op_dir,
2024                PrinterOutputter::new(),
2025                heap_loc_as_cell!(0),
2026            );
2027
2028            printer.max_depth = 5;
2029
2030            let output = printer.print();
2031
2032            assert_eq!(output.result(), "[_1,_3,_5,_7,_9|...]");
2033        }
2034
2035        all_cells_unmarked(&wam.machine_st.heap);
2036
2037        wam.machine_st.heap.clear();
2038
2039        put_partial_string(&mut wam.machine_st.heap, "abc", &wam.machine_st.atom_tbl);
2040
2041        {
2042            let printer = HCPrinter::new(
2043                &mut wam.machine_st.heap,
2044                Arc::clone(&wam.machine_st.atom_tbl),
2045                &mut wam.machine_st.stack,
2046                &wam.op_dir,
2047                PrinterOutputter::new(),
2048                pstr_loc_as_cell!(0),
2049            );
2050
2051            let output = printer.print();
2052
2053            assert_eq!(output.result(), "[a,b,c|_1]");
2054        }
2055
2056        all_cells_unmarked(&wam.machine_st.heap);
2057
2058        wam.machine_st.heap.pop();
2059
2060        wam.machine_st.heap.push(list_loc_as_cell!(2));
2061
2062        wam.machine_st.heap.push(atom_as_cell!(a_atom));
2063        wam.machine_st.heap.push(list_loc_as_cell!(4));
2064        wam.machine_st.heap.push(atom_as_cell!(b_atom));
2065        wam.machine_st.heap.push(list_loc_as_cell!(6));
2066        wam.machine_st.heap.push(atom_as_cell!(c_atom));
2067        wam.machine_st.heap.push(empty_list_as_cell!());
2068
2069        {
2070            let mut printer = HCPrinter::new(
2071                &mut wam.machine_st.heap,
2072                Arc::clone(&wam.machine_st.atom_tbl),
2073                &mut wam.machine_st.stack,
2074                &wam.op_dir,
2075                PrinterOutputter::new(),
2076                heap_loc_as_cell!(0),
2077            );
2078
2079            printer.double_quotes = true;
2080
2081            let output = printer.print();
2082
2083            assert_eq!(output.result(), "\"abcabc\"");
2084        }
2085
2086        all_cells_unmarked(&wam.machine_st.heap);
2087
2088        wam.machine_st.heap.clear();
2089
2090        assert_eq!(
2091            &wam.parse_and_print_term("=(X,[a,b,c|X]).").unwrap(),
2092            "=(X,[a,b,c|X])"
2093        );
2094
2095        all_cells_unmarked(&wam.machine_st.heap);
2096
2097        assert_eq!(
2098            &wam.parse_and_print_term("[a,b,\"a\",[a,b,c]].").unwrap(),
2099            "[a,b,[a],[a,b,c]]"
2100        );
2101
2102        all_cells_unmarked(&wam.machine_st.heap);
2103
2104        assert_eq!(
2105            &wam.parse_and_print_term("[\"abc\",e,f,[g,e,h,Y,v|[X,Y]]].")
2106                .unwrap(),
2107            "[[a,b,c],e,f,[g,e,h,Y,v,X,Y]]"
2108        );
2109
2110        all_cells_unmarked(&wam.machine_st.heap);
2111
2112        assert_eq!(&wam.parse_and_print_term("f((a,b)).").unwrap(), "f((a,b))");
2113
2114        all_cells_unmarked(&wam.machine_st.heap);
2115
2116        wam.op_dir
2117            .insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX as u8));
2118        wam.op_dir
2119            .insert((atom!("*"), Fixity::In), OpDesc::build_with(400, YFX as u8));
2120
2121        assert_eq!(
2122            &wam.parse_and_print_term("[a|[] + b].").unwrap(),
2123            "[a|[]+b]"
2124        );
2125
2126        all_cells_unmarked(&wam.machine_st.heap);
2127
2128        assert_eq!(
2129            &wam.parse_and_print_term("[a|[b|c]*d].").unwrap(),
2130            "[a|[b|c]*d]"
2131        );
2132
2133        all_cells_unmarked(&wam.machine_st.heap);
2134
2135        wam.op_dir
2136            .insert((atom!("fy"), Fixity::Pre), OpDesc::build_with(9, FY as u8));
2137
2138        wam.op_dir
2139            .insert((atom!("yf"), Fixity::Post), OpDesc::build_with(9, YF as u8));
2140
2141        assert_eq!(
2142            &wam.parse_and_print_term("(fy (fy 1)yf)yf.").unwrap(),
2143            "(fy (fy 1)yf)yf"
2144        );
2145
2146        assert_eq!(
2147            &wam.parse_and_print_term("fy(fy(yf(fy(1)))).").unwrap(),
2148            "fy fy (fy 1)yf"
2149        );
2150    }
2151}