sway_ir/
printer.rs

1//! Print (or serialize) IR to human and machine readable text.
2//!
3//! This module implements a document based pretty-printer.  A couple of 3rd party pretty printing
4//! crates were assessed but didn't seem to work as well as this simple version, which is quite
5//! effective.
6
7use std::collections::{BTreeMap, HashMap};
8
9use sway_types::SourceEngine;
10
11use crate::{
12    asm::*,
13    block::Block,
14    constant::{ConstantContent, ConstantValue},
15    context::Context,
16    function::{Function, FunctionContent},
17    instruction::{FuelVmInstruction, InstOp, Predicate, Register},
18    metadata::{MetadataIndex, Metadatum},
19    module::{Kind, ModuleContent},
20    value::{Value, ValueContent, ValueDatum},
21    AnalysisResult, AnalysisResultT, AnalysisResults, BinaryOpKind, BlockArgument, ConfigContent,
22    IrError, Module, Pass, PassMutability, ScopedPass, UnaryOpKind,
23};
24
25#[derive(Debug)]
26pub(crate) enum Doc {
27    Empty,
28    Space,
29    Comma,
30
31    Text(String),
32    Line(Box<Doc>),
33
34    Pair(Box<Doc>, Box<Doc>),
35
36    List(Vec<Doc>),
37    ListSep(Vec<Doc>, Box<Doc>),
38
39    Parens(Box<Doc>),
40
41    Indent(i64, Box<Doc>),
42}
43
44impl Doc {
45    pub(crate) fn text<S: Into<String>>(s: S) -> Self {
46        Doc::Text(s.into())
47    }
48
49    fn line(doc: Doc) -> Self {
50        Doc::Line(Box::new(doc))
51    }
52
53    pub(crate) fn text_line<S: Into<String>>(s: S) -> Self {
54        Doc::Line(Box::new(Doc::Text(s.into())))
55    }
56
57    fn indent(n: i64, doc: Doc) -> Doc {
58        Doc::Indent(n, Box::new(doc))
59    }
60
61    fn list_sep(docs: Vec<Doc>, sep: Doc) -> Doc {
62        Doc::ListSep(docs, Box::new(sep))
63    }
64
65    fn in_parens_comma_sep(docs: Vec<Doc>) -> Doc {
66        Doc::Parens(Box::new(Doc::list_sep(docs, Doc::Comma)))
67    }
68
69    pub(crate) fn append(self, doc: Doc) -> Doc {
70        match (&self, &doc) {
71            (Doc::Empty, _) => doc,
72            (_, Doc::Empty) => self,
73            _ => Doc::Pair(Box::new(self), Box::new(doc)),
74        }
75    }
76
77    fn and(self, doc: Doc) -> Doc {
78        match doc {
79            Doc::Empty => doc,
80            _ => Doc::Pair(Box::new(self), Box::new(doc)),
81        }
82    }
83
84    pub(crate) fn build(self) -> String {
85        build_doc(self, 0)
86    }
87}
88
89/// Pretty-print a whole [`Context`] to a string.
90///
91/// The output from this function must always be suitable for [crate::parser::parse].
92pub fn to_string(context: &Context) -> String {
93    context_print(context, &|_, doc| doc)
94}
95
96pub(crate) fn context_print(context: &Context, map_doc: &impl Fn(&Value, Doc) -> Doc) -> String {
97    let mut md_namer = MetadataNamer::default();
98    context
99        .modules
100        .iter()
101        .fold(Doc::Empty, |doc, (_, module)| {
102            doc.append(module_to_doc(context, &mut md_namer, module, map_doc))
103        })
104        .append(md_namer.to_doc(context))
105        .build()
106}
107
108pub(crate) fn block_print(
109    context: &Context,
110    function: Function,
111    block: Block,
112    map_doc: &impl Fn(&Value, Doc) -> Doc,
113) -> String {
114    let mut md_namer = MetadataNamer::default();
115    let mut namer = Namer::new(function);
116    block_to_doc(context, &mut md_namer, &mut namer, &block, map_doc).build()
117}
118
119pub struct ModulePrinterResult;
120impl AnalysisResultT for ModulePrinterResult {}
121
122/// Pass to print a module to stdout.
123pub fn module_printer_pass(
124    context: &Context,
125    _analyses: &AnalysisResults,
126    module: Module,
127) -> Result<AnalysisResult, IrError> {
128    let mut md_namer = MetadataNamer::default();
129    print!(
130        "{}",
131        module_to_doc(
132            context,
133            &mut md_namer,
134            context.modules.get(module.0).unwrap(),
135            &|_, doc| doc
136        )
137        .append(md_namer.to_doc(context))
138        .build()
139    );
140    Ok(Box::new(ModulePrinterResult))
141}
142
143/// Print a module to stdout.
144pub fn module_print(context: &Context, _analyses: &AnalysisResults, module: Module) {
145    let mut md_namer = MetadataNamer::default();
146    println!(
147        "{}",
148        module_to_doc(
149            context,
150            &mut md_namer,
151            context.modules.get(module.0).unwrap(),
152            &|_, doc| doc
153        )
154        .append(md_namer.to_doc(context))
155        .build()
156    );
157}
158
159/// Print a function to stdout.
160pub fn function_print(context: &Context, function: Function) {
161    let mut md_namer = MetadataNamer::default();
162    println!(
163        "{}",
164        function_to_doc(
165            context,
166            &mut md_namer,
167            &mut Namer::new(function),
168            context.functions.get(function.0).unwrap(),
169            &|_, doc| doc
170        )
171        .append(md_namer.to_doc(context))
172        .build()
173    );
174}
175
176pub const MODULE_PRINTER_NAME: &str = "module-printer";
177
178pub fn create_module_printer_pass() -> Pass {
179    Pass {
180        name: MODULE_PRINTER_NAME,
181        descr: "Print module to stdout",
182        deps: vec![],
183        runner: ScopedPass::ModulePass(PassMutability::Analysis(module_printer_pass)),
184    }
185}
186
187fn module_to_doc<'a>(
188    context: &'a Context,
189    md_namer: &mut MetadataNamer,
190    module: &'a ModuleContent,
191    map_doc: &impl Fn(&Value, Doc) -> Doc,
192) -> Doc {
193    Doc::line(Doc::Text(format!(
194        "{} {{",
195        match module.kind {
196            Kind::Contract => "contract",
197            Kind::Library => "library",
198            Kind::Predicate => "predicate",
199            Kind::Script => "script",
200        }
201    )))
202    .append(Doc::indent(
203        4,
204        Doc::List(
205            module
206                .configs
207                .values()
208                .map(|value| config_to_doc(context, value, md_namer))
209                .collect(),
210        ),
211    ))
212    .append(if !module.configs.is_empty() {
213        Doc::line(Doc::Empty)
214    } else {
215        Doc::Empty
216    })
217    .append(Doc::indent(
218        4,
219        Doc::list_sep(
220            module
221                .global_variables
222                .iter()
223                .map(|(name, var)| {
224                    let var_content = &context.global_vars[var.0];
225                    let init_doc = match &var_content.initializer {
226                        Some(const_val) => Doc::text(format!(
227                            " = const {}",
228                            const_val.get_content(context).as_lit_string(context)
229                        )),
230                        None => Doc::Empty,
231                    };
232                    let mut_string = if var_content.mutable { "mut " } else { "" };
233                    Doc::line(
234                        Doc::text(format!(
235                            "{}global {} : {}",
236                            mut_string,
237                            name.join("::"),
238                            var.get_inner_type(context).as_string(context),
239                        ))
240                        .append(init_doc),
241                    )
242                })
243                .collect(),
244            Doc::line(Doc::Empty),
245        ),
246    ))
247    .append(Doc::indent(
248        4,
249        Doc::list_sep(
250            module
251                .functions
252                .iter()
253                .map(|function| {
254                    function_to_doc(
255                        context,
256                        md_namer,
257                        &mut Namer::new(*function),
258                        &context.functions[function.0],
259                        map_doc,
260                    )
261                })
262                .collect(),
263            Doc::line(Doc::Empty),
264        ),
265    ))
266    .append(Doc::text_line("}"))
267}
268
269fn config_to_doc(
270    context: &Context,
271    configurable: &ConfigContent,
272    md_namer: &mut MetadataNamer,
273) -> Doc {
274    match configurable {
275        ConfigContent::V0 {
276            name,
277            constant,
278            opt_metadata,
279            ..
280        } => Doc::line(
281            Doc::text(format!(
282                "{} = config {}",
283                name,
284                constant.get_content(context).as_lit_string(context)
285            ))
286            .append(md_namer.md_idx_to_doc(context, opt_metadata)),
287        ),
288        ConfigContent::V1 {
289            name,
290            ty,
291            encoded_bytes,
292            decode_fn,
293            opt_metadata,
294            ..
295        } => {
296            let ty = ty.as_string(context);
297            let bytes = encoded_bytes
298                .iter()
299                .map(|b| format!("{b:02x}"))
300                .collect::<Vec<String>>()
301                .concat();
302            Doc::line(
303                Doc::text(format!(
304                    "{} = config {}, {}, 0x{}",
305                    name,
306                    ty,
307                    decode_fn.get().get_name(context),
308                    bytes,
309                ))
310                .append(md_namer.md_idx_to_doc(context, opt_metadata)),
311            )
312        }
313    }
314}
315
316fn function_to_doc<'a>(
317    context: &'a Context,
318    md_namer: &mut MetadataNamer,
319    namer: &mut Namer,
320    function: &'a FunctionContent,
321    map_doc: &impl Fn(&Value, Doc) -> Doc,
322) -> Doc {
323    let public = if function.is_public { "pub " } else { "" };
324    let entry = if function.is_entry { "entry " } else { "" };
325    // TODO: Remove outer `if` once old encoding is fully removed.
326    //       This is an intentional "complication" so that we see
327    //       explicit using of `new_encoding` here.
328    //       For the time being, for the old encoding, we don't want
329    //       to show both `entry` and `entry_orig` although both
330    //       values will be true.
331    // TODO: When removing old encoding, remove also the TODO in the
332    //       `rule fn_decl()` definition of the IR parser.
333    let original_entry = if context.experimental.new_encoding {
334        if function.is_original_entry {
335            "entry_orig "
336        } else {
337            ""
338        }
339    } else if !function.is_entry && function.is_original_entry {
340        "entry_orig "
341    } else {
342        ""
343    };
344    let fallback = if function.is_fallback {
345        "fallback "
346    } else {
347        ""
348    };
349    Doc::line(
350        Doc::text(format!(
351            "{}{}{}{}fn {}",
352            public, entry, original_entry, fallback, function.name
353        ))
354        .append(
355            function
356                .selector
357                .map(|bytes| {
358                    Doc::text(format!(
359                        "<{:02x}{:02x}{:02x}{:02x}>",
360                        bytes[0], bytes[1], bytes[2], bytes[3]
361                    ))
362                })
363                .unwrap_or(Doc::Empty),
364        )
365        .append(Doc::in_parens_comma_sep(
366            function
367                .arguments
368                .iter()
369                .map(|(name, arg_val)| {
370                    if let ValueContent {
371                        value: ValueDatum::Argument(BlockArgument { ty, .. }),
372                        metadata,
373                        ..
374                    } = &context.values[arg_val.0]
375                    {
376                        Doc::text(name)
377                            .append(
378                                Doc::Space.and(md_namer.md_idx_to_doc_no_comma(context, metadata)),
379                            )
380                            .append(Doc::text(format!(": {}", ty.as_string(context))))
381                    } else {
382                        unreachable!("Unexpected non argument value for function arguments.")
383                    }
384                })
385                .collect(),
386        ))
387        .append(Doc::text(format!(
388            " -> {}",
389            function.return_type.as_string(context)
390        )))
391        .append(md_namer.md_idx_to_doc(context, &function.metadata))
392        .append(Doc::text(" {")),
393    )
394    .append(Doc::indent(
395        4,
396        Doc::list_sep(
397            vec![
398                Doc::List(
399                    function
400                        .local_storage
401                        .iter()
402                        .map(|(name, var)| {
403                            let var_content = &context.local_vars[var.0];
404                            let init_doc = match &var_content.initializer {
405                                Some(const_val) => Doc::text(format!(
406                                    " = const {}",
407                                    const_val.get_content(context).as_lit_string(context)
408                                )),
409                                None => Doc::Empty,
410                            };
411                            let mut_str = if var_content.mutable { "mut " } else { "" };
412                            Doc::line(
413                                // Print the inner, pointed-to type in the locals list.
414                                Doc::text(format!(
415                                    "local {mut_str}{} {name}",
416                                    var.get_inner_type(context).as_string(context)
417                                ))
418                                .append(init_doc),
419                            )
420                        })
421                        .collect(),
422                ),
423                Doc::list_sep(
424                    function
425                        .blocks
426                        .iter()
427                        .map(|block| block_to_doc(context, md_namer, namer, block, map_doc))
428                        .collect(),
429                    Doc::line(Doc::Empty),
430                ),
431            ],
432            Doc::line(Doc::Empty),
433        ),
434    ))
435    .append(Doc::text_line("}"))
436}
437
438fn block_to_doc(
439    context: &Context,
440    md_namer: &mut MetadataNamer,
441    namer: &mut Namer,
442    block: &Block,
443    map_doc: &impl Fn(&Value, Doc) -> Doc,
444) -> Doc {
445    let block_content = &context.blocks[block.0];
446    Doc::line(
447        Doc::text(block_content.label.to_string()).append(
448            Doc::in_parens_comma_sep(
449                block
450                    .arg_iter(context)
451                    .map(|arg_val| {
452                        Doc::text(namer.name(context, arg_val)).append(Doc::text(format!(
453                            ": {}",
454                            arg_val.get_type(context).unwrap().as_string(context)
455                        )))
456                    })
457                    .collect(),
458            )
459            .append(Doc::Text(":".to_string())),
460        ),
461    )
462    .append(Doc::List(
463        block
464            .instruction_iter(context)
465            .map(|current_value| {
466                let doc = instruction_to_doc(context, md_namer, namer, block, &current_value);
467                (map_doc)(&current_value, doc)
468            })
469            .collect(),
470    ))
471}
472
473fn constant_to_doc(
474    context: &Context,
475    md_namer: &mut MetadataNamer,
476    namer: &mut Namer,
477    const_val: &Value,
478) -> Doc {
479    if let ValueContent {
480        value: ValueDatum::Constant(constant),
481        metadata,
482    } = &context.values[const_val.0]
483    {
484        Doc::line(
485            Doc::text(format!(
486                "{} = const {}",
487                namer.name(context, const_val),
488                constant.get_content(context).as_lit_string(context)
489            ))
490            .append(md_namer.md_idx_to_doc(context, metadata)),
491        )
492    } else {
493        unreachable!("Not a constant value.")
494    }
495}
496
497fn maybe_constant_to_doc(
498    context: &Context,
499    md_namer: &mut MetadataNamer,
500    namer: &mut Namer,
501    maybe_const_val: &Value,
502) -> Doc {
503    // Create a new doc only if value is new and unknown, and is a constant.
504    if !namer.is_known(maybe_const_val) && maybe_const_val.is_constant(context) {
505        constant_to_doc(context, md_namer, namer, maybe_const_val)
506    } else {
507        Doc::Empty
508    }
509}
510
511fn instruction_to_doc<'a>(
512    context: &'a Context,
513    md_namer: &mut MetadataNamer,
514    namer: &mut Namer,
515    block: &Block,
516    ins_value: &'a Value,
517) -> Doc {
518    if let ValueContent {
519        value: ValueDatum::Instruction(instruction),
520        metadata,
521    } = &context.values[ins_value.0]
522    {
523        match &instruction.op {
524            InstOp::AsmBlock(asm, args) => {
525                asm_block_to_doc(context, md_namer, namer, ins_value, asm, args, metadata)
526            }
527            InstOp::BitCast(value, ty) => maybe_constant_to_doc(context, md_namer, namer, value)
528                .append(Doc::line(
529                    Doc::text(format!(
530                        "{} = bitcast {} to {}",
531                        namer.name(context, ins_value),
532                        namer.name(context, value),
533                        ty.as_string(context),
534                    ))
535                    .append(md_namer.md_idx_to_doc(context, metadata)),
536                )),
537            InstOp::UnaryOp { op, arg } => {
538                let op_str = match op {
539                    UnaryOpKind::Not => "not",
540                };
541                maybe_constant_to_doc(context, md_namer, namer, arg).append(Doc::line(
542                    Doc::text(format!(
543                        "{} = {op_str} {}",
544                        namer.name(context, ins_value),
545                        namer.name(context, arg),
546                    ))
547                    .append(md_namer.md_idx_to_doc(context, metadata)),
548                ))
549            }
550            InstOp::BinaryOp { op, arg1, arg2 } => {
551                let op_str = match op {
552                    BinaryOpKind::Add => "add",
553                    BinaryOpKind::Sub => "sub",
554                    BinaryOpKind::Mul => "mul",
555                    BinaryOpKind::Div => "div",
556                    BinaryOpKind::And => "and",
557                    BinaryOpKind::Or => "or",
558                    BinaryOpKind::Xor => "xor",
559                    BinaryOpKind::Mod => "mod",
560                    BinaryOpKind::Rsh => "rsh",
561                    BinaryOpKind::Lsh => "lsh",
562                };
563                maybe_constant_to_doc(context, md_namer, namer, arg1)
564                    .append(maybe_constant_to_doc(context, md_namer, namer, arg2))
565                    .append(Doc::line(
566                        Doc::text(format!(
567                            "{} = {op_str} {}, {}",
568                            namer.name(context, ins_value),
569                            namer.name(context, arg1),
570                            namer.name(context, arg2),
571                        ))
572                        .append(md_namer.md_idx_to_doc(context, metadata)),
573                    ))
574            }
575            InstOp::Branch(to_block) =>
576            // Handle possibly constant block parameters
577            {
578                to_block
579                    .args
580                    .iter()
581                    .fold(Doc::Empty, |doc, param| {
582                        doc.append(maybe_constant_to_doc(context, md_namer, namer, param))
583                    })
584                    .append(Doc::line(
585                        Doc::text(format!("br {}", context.blocks[to_block.block.0].label,))
586                            .append(
587                                Doc::in_parens_comma_sep(
588                                    to_block
589                                        .args
590                                        .iter()
591                                        .map(|arg_val| Doc::text(namer.name(context, arg_val)))
592                                        .collect(),
593                                )
594                                .append(md_namer.md_idx_to_doc(context, metadata)),
595                            ),
596                    ))
597            }
598            InstOp::Call(func, args) => args
599                .iter()
600                .fold(Doc::Empty, |doc, arg_val| {
601                    doc.append(maybe_constant_to_doc(context, md_namer, namer, arg_val))
602                })
603                .append(Doc::line(
604                    Doc::text(format!(
605                        "{} = call {}",
606                        namer.name(context, ins_value),
607                        context.functions[func.0].name
608                    ))
609                    .append(Doc::in_parens_comma_sep(
610                        args.iter()
611                            .map(|arg_val| Doc::text(namer.name(context, arg_val)))
612                            .collect(),
613                    ))
614                    .append(md_namer.md_idx_to_doc(context, metadata)),
615                )),
616            InstOp::CastPtr(val, ty) => Doc::line(
617                Doc::text(format!(
618                    "{} = cast_ptr {} to {}",
619                    namer.name(context, ins_value),
620                    namer.name(context, val),
621                    ty.as_string(context)
622                ))
623                .append(md_namer.md_idx_to_doc(context, metadata)),
624            ),
625            InstOp::Cmp(pred, lhs_value, rhs_value) => {
626                let pred_str = match pred {
627                    Predicate::Equal => "eq",
628                    Predicate::LessThan => "lt",
629                    Predicate::GreaterThan => "gt",
630                };
631                maybe_constant_to_doc(context, md_namer, namer, lhs_value)
632                    .append(maybe_constant_to_doc(context, md_namer, namer, rhs_value))
633                    .append(Doc::line(
634                        Doc::text(format!(
635                            "{} = cmp {pred_str} {} {}",
636                            namer.name(context, ins_value),
637                            namer.name(context, lhs_value),
638                            namer.name(context, rhs_value),
639                        ))
640                        .append(md_namer.md_idx_to_doc(context, metadata)),
641                    ))
642            }
643            InstOp::ConditionalBranch {
644                cond_value,
645                true_block,
646                false_block,
647            } => {
648                let true_label = &context.blocks[true_block.block.0].label;
649                let false_label = &context.blocks[false_block.block.0].label;
650                // Handle possibly constant block parameters
651                let doc = true_block.args.iter().fold(
652                    maybe_constant_to_doc(context, md_namer, namer, cond_value),
653                    |doc, param| doc.append(maybe_constant_to_doc(context, md_namer, namer, param)),
654                );
655                let doc = false_block.args.iter().fold(doc, |doc, param| {
656                    doc.append(maybe_constant_to_doc(context, md_namer, namer, param))
657                });
658                doc.append(Doc::line(
659                    Doc::text(format!("cbr {}", namer.name(context, cond_value),)).append(
660                        Doc::text(format!(", {true_label}")).append(
661                            Doc::in_parens_comma_sep(
662                                true_block
663                                    .args
664                                    .iter()
665                                    .map(|arg_val| Doc::text(namer.name(context, arg_val)))
666                                    .collect(),
667                            )
668                            .append(
669                                Doc::text(format!(", {false_label}")).append(
670                                    Doc::in_parens_comma_sep(
671                                        false_block
672                                            .args
673                                            .iter()
674                                            .map(|arg_val| Doc::text(namer.name(context, arg_val)))
675                                            .collect(),
676                                    )
677                                    .append(md_namer.md_idx_to_doc(context, metadata)),
678                                ),
679                            ),
680                        ),
681                    ),
682                ))
683            }
684            InstOp::ContractCall {
685                return_type,
686                name,
687                params,
688                coins,
689                asset_id,
690                gas,
691            } => maybe_constant_to_doc(context, md_namer, namer, coins)
692                .append(maybe_constant_to_doc(context, md_namer, namer, asset_id))
693                .append(maybe_constant_to_doc(context, md_namer, namer, gas))
694                .append(Doc::line(
695                    Doc::text(format!(
696                        "{} = contract_call {} {} {}, {}, {}, {}",
697                        namer.name(context, ins_value),
698                        return_type.as_string(context),
699                        name.as_deref().unwrap_or(""),
700                        namer.name(context, params),
701                        namer.name(context, coins),
702                        namer.name(context, asset_id),
703                        namer.name(context, gas),
704                    ))
705                    .append(md_namer.md_idx_to_doc(context, metadata)),
706                )),
707            InstOp::FuelVm(fuel_vm_instr) => match fuel_vm_instr {
708                FuelVmInstruction::Gtf { index, tx_field_id } => {
709                    maybe_constant_to_doc(context, md_namer, namer, index).append(Doc::line(
710                        Doc::text(format!(
711                            "{} = gtf {}, {}",
712                            namer.name(context, ins_value),
713                            namer.name(context, index),
714                            tx_field_id,
715                        ))
716                        .append(md_namer.md_idx_to_doc(context, metadata)),
717                    ))
718                }
719                FuelVmInstruction::Log {
720                    log_val,
721                    log_ty,
722                    log_id,
723                } => maybe_constant_to_doc(context, md_namer, namer, log_val)
724                    .append(maybe_constant_to_doc(context, md_namer, namer, log_id))
725                    .append(Doc::line(
726                        Doc::text(format!(
727                            "log {} {}, {}",
728                            log_ty.as_string(context),
729                            namer.name(context, log_val),
730                            namer.name(context, log_id),
731                        ))
732                        .append(md_namer.md_idx_to_doc(context, metadata)),
733                    )),
734                FuelVmInstruction::ReadRegister(reg) => Doc::line(
735                    Doc::text(format!(
736                        "{} = read_register {}",
737                        namer.name(context, ins_value),
738                        match reg {
739                            Register::Of => "of",
740                            Register::Pc => "pc",
741                            Register::Ssp => "ssp",
742                            Register::Sp => "sp",
743                            Register::Fp => "fp",
744                            Register::Hp => "hp",
745                            Register::Error => "err",
746                            Register::Ggas => "ggas",
747                            Register::Cgas => "cgas",
748                            Register::Bal => "bal",
749                            Register::Is => "is",
750                            Register::Ret => "ret",
751                            Register::Retl => "retl",
752                            Register::Flag => "flag",
753                        },
754                    ))
755                    .append(md_namer.md_idx_to_doc(context, metadata)),
756                ),
757                FuelVmInstruction::Revert(v) => maybe_constant_to_doc(context, md_namer, namer, v)
758                    .append(Doc::line(
759                        Doc::text(format!("revert {}", namer.name(context, v),))
760                            .append(md_namer.md_idx_to_doc(context, metadata)),
761                    )),
762                FuelVmInstruction::JmpMem => Doc::line(
763                    Doc::text("jmp_mem".to_string())
764                        .append(md_namer.md_idx_to_doc(context, metadata)),
765                ),
766                FuelVmInstruction::Smo {
767                    recipient,
768                    message,
769                    message_size,
770                    coins,
771                } => maybe_constant_to_doc(context, md_namer, namer, recipient)
772                    .append(maybe_constant_to_doc(context, md_namer, namer, message))
773                    .append(maybe_constant_to_doc(
774                        context,
775                        md_namer,
776                        namer,
777                        message_size,
778                    ))
779                    .append(maybe_constant_to_doc(context, md_namer, namer, coins))
780                    .append(Doc::line(
781                        Doc::text(format!(
782                            "smo {}, {}, {}, {}",
783                            namer.name(context, recipient),
784                            namer.name(context, message),
785                            namer.name(context, message_size),
786                            namer.name(context, coins),
787                        ))
788                        .append(md_namer.md_idx_to_doc(context, metadata)),
789                    )),
790                FuelVmInstruction::StateClear {
791                    key,
792                    number_of_slots,
793                } => maybe_constant_to_doc(context, md_namer, namer, number_of_slots).append(
794                    Doc::line(
795                        Doc::text(format!(
796                            "state_clear key {}, {}",
797                            namer.name(context, key),
798                            namer.name(context, number_of_slots),
799                        ))
800                        .append(md_namer.md_idx_to_doc(context, metadata)),
801                    ),
802                ),
803                FuelVmInstruction::StateLoadQuadWord {
804                    load_val,
805                    key,
806                    number_of_slots,
807                } => maybe_constant_to_doc(context, md_namer, namer, number_of_slots).append(
808                    Doc::line(
809                        Doc::text(format!(
810                            "{} = state_load_quad_word {}, key {}, {}",
811                            namer.name(context, ins_value),
812                            namer.name(context, load_val),
813                            namer.name(context, key),
814                            namer.name(context, number_of_slots),
815                        ))
816                        .append(md_namer.md_idx_to_doc(context, metadata)),
817                    ),
818                ),
819                FuelVmInstruction::StateLoadWord(key) => Doc::line(
820                    Doc::text(format!(
821                        "{} = state_load_word key {}",
822                        namer.name(context, ins_value),
823                        namer.name(context, key),
824                    ))
825                    .append(md_namer.md_idx_to_doc(context, metadata)),
826                ),
827                FuelVmInstruction::StateStoreQuadWord {
828                    stored_val,
829                    key,
830                    number_of_slots,
831                } => maybe_constant_to_doc(context, md_namer, namer, number_of_slots).append(
832                    Doc::line(
833                        Doc::text(format!(
834                            "{} = state_store_quad_word {}, key {}, {}",
835                            namer.name(context, ins_value),
836                            namer.name(context, stored_val),
837                            namer.name(context, key),
838                            namer.name(context, number_of_slots),
839                        ))
840                        .append(md_namer.md_idx_to_doc(context, metadata)),
841                    ),
842                ),
843                FuelVmInstruction::StateStoreWord { stored_val, key } => {
844                    maybe_constant_to_doc(context, md_namer, namer, stored_val).append(Doc::line(
845                        Doc::text(format!(
846                            "{} = state_store_word {}, key {}",
847                            namer.name(context, ins_value),
848                            namer.name(context, stored_val),
849                            namer.name(context, key),
850                        ))
851                        .append(md_namer.md_idx_to_doc(context, metadata)),
852                    ))
853                }
854                FuelVmInstruction::WideUnaryOp { op, arg, result } => {
855                    let op_str = match op {
856                        UnaryOpKind::Not => "not",
857                    };
858                    maybe_constant_to_doc(context, md_namer, namer, arg).append(Doc::line(
859                        Doc::text(format!(
860                            "wide {op_str} {} to {}",
861                            namer.name(context, arg),
862                            namer.name(context, result),
863                        ))
864                        .append(md_namer.md_idx_to_doc(context, metadata)),
865                    ))
866                }
867                FuelVmInstruction::WideBinaryOp {
868                    op,
869                    arg1,
870                    arg2,
871                    result,
872                } => {
873                    let op_str = match op {
874                        BinaryOpKind::Add => "add",
875                        BinaryOpKind::Sub => "sub",
876                        BinaryOpKind::Mul => "mul",
877                        BinaryOpKind::Div => "div",
878                        BinaryOpKind::And => "and",
879                        BinaryOpKind::Or => "or",
880                        BinaryOpKind::Xor => "xor",
881                        BinaryOpKind::Mod => "mod",
882                        BinaryOpKind::Rsh => "rsh",
883                        BinaryOpKind::Lsh => "lsh",
884                    };
885                    maybe_constant_to_doc(context, md_namer, namer, arg1)
886                        .append(maybe_constant_to_doc(context, md_namer, namer, arg2))
887                        .append(Doc::line(
888                            Doc::text(format!(
889                                "wide {op_str} {}, {} to {}",
890                                namer.name(context, arg1),
891                                namer.name(context, arg2),
892                                namer.name(context, result),
893                            ))
894                            .append(md_namer.md_idx_to_doc(context, metadata)),
895                        ))
896                }
897                FuelVmInstruction::WideModularOp {
898                    op,
899                    result,
900                    arg1,
901                    arg2,
902                    arg3,
903                } => {
904                    let op_str = match op {
905                        BinaryOpKind::Mod => "mod",
906                        _ => unreachable!(),
907                    };
908                    maybe_constant_to_doc(context, md_namer, namer, arg1)
909                        .append(maybe_constant_to_doc(context, md_namer, namer, arg2))
910                        .append(maybe_constant_to_doc(context, md_namer, namer, arg3))
911                        .append(Doc::line(
912                            Doc::text(format!(
913                                "wide {op_str} {}, {}, {} to {}",
914                                namer.name(context, arg1),
915                                namer.name(context, arg2),
916                                namer.name(context, arg3),
917                                namer.name(context, result),
918                            ))
919                            .append(md_namer.md_idx_to_doc(context, metadata)),
920                        ))
921                }
922                FuelVmInstruction::WideCmpOp { op, arg1, arg2 } => {
923                    let pred_str = match op {
924                        Predicate::Equal => "eq",
925                        Predicate::LessThan => "lt",
926                        Predicate::GreaterThan => "gt",
927                    };
928                    maybe_constant_to_doc(context, md_namer, namer, arg1)
929                        .append(maybe_constant_to_doc(context, md_namer, namer, arg2))
930                        .append(Doc::line(
931                            Doc::text(format!(
932                                "{} = wide cmp {pred_str} {} {}",
933                                namer.name(context, ins_value),
934                                namer.name(context, arg1),
935                                namer.name(context, arg2),
936                            ))
937                            .append(md_namer.md_idx_to_doc(context, metadata)),
938                        ))
939                }
940                FuelVmInstruction::Retd { ptr, len } => {
941                    maybe_constant_to_doc(context, md_namer, namer, ptr)
942                        .append(maybe_constant_to_doc(context, md_namer, namer, len))
943                        .append(Doc::line(
944                            Doc::text(format!(
945                                "retd {} {}",
946                                namer.name(context, ptr),
947                                namer.name(context, len),
948                            ))
949                            .append(md_namer.md_idx_to_doc(context, metadata)),
950                        ))
951                }
952            },
953            InstOp::GetElemPtr {
954                base,
955                elem_ptr_ty,
956                indices,
957            } => indices
958                .iter()
959                .fold(Doc::Empty, |acc, idx| {
960                    acc.append(maybe_constant_to_doc(context, md_namer, namer, idx))
961                })
962                .append(Doc::line(
963                    Doc::text(format!(
964                        "{} = get_elem_ptr {}, {}, ",
965                        namer.name(context, ins_value),
966                        namer.name(context, base),
967                        elem_ptr_ty.as_string(context),
968                    ))
969                    .append(Doc::list_sep(
970                        indices
971                            .iter()
972                            .map(|idx| Doc::text(namer.name(context, idx)))
973                            .collect(),
974                        Doc::Comma,
975                    ))
976                    .append(md_namer.md_idx_to_doc(context, metadata)),
977                )),
978            InstOp::GetLocal(local_var) => {
979                let name = block
980                    .get_function(context)
981                    .lookup_local_name(context, local_var)
982                    .unwrap();
983                Doc::line(
984                    Doc::text(format!(
985                        "{} = get_local {}, {name}",
986                        namer.name(context, ins_value),
987                        local_var.get_type(context).as_string(context),
988                    ))
989                    .append(md_namer.md_idx_to_doc(context, metadata)),
990                )
991            }
992            InstOp::GetGlobal(global_var) => {
993                let name = block
994                    .get_function(context)
995                    .get_module(context)
996                    .lookup_global_variable_name(context, global_var)
997                    .unwrap();
998                Doc::line(
999                    Doc::text(format!(
1000                        "{} = get_global {}, {name}",
1001                        namer.name(context, ins_value),
1002                        global_var.get_type(context).as_string(context),
1003                    ))
1004                    .append(md_namer.md_idx_to_doc(context, metadata)),
1005                )
1006            }
1007            InstOp::GetConfig(_, name) => Doc::line(
1008                match block.get_module(context).get_config(context, name).unwrap() {
1009                    ConfigContent::V0 { name, ptr_ty, .. }
1010                    | ConfigContent::V1 { name, ptr_ty, .. } => Doc::text(format!(
1011                        "{} = get_config {}, {}",
1012                        namer.name(context, ins_value),
1013                        ptr_ty.as_string(context),
1014                        name,
1015                    )),
1016                }
1017                .append(md_namer.md_idx_to_doc(context, metadata)),
1018            ),
1019            InstOp::IntToPtr(value, ty) => maybe_constant_to_doc(context, md_namer, namer, value)
1020                .append(Doc::line(
1021                    Doc::text(format!(
1022                        "{} = int_to_ptr {} to {}",
1023                        namer.name(context, ins_value),
1024                        namer.name(context, value),
1025                        ty.as_string(context),
1026                    ))
1027                    .append(md_namer.md_idx_to_doc(context, metadata)),
1028                )),
1029            InstOp::Load(src_value) => Doc::line(
1030                Doc::text(format!(
1031                    "{} = load {}",
1032                    namer.name(context, ins_value),
1033                    namer.name(context, src_value),
1034                ))
1035                .append(md_namer.md_idx_to_doc(context, metadata)),
1036            ),
1037            InstOp::MemCopyBytes {
1038                dst_val_ptr,
1039                src_val_ptr,
1040                byte_len,
1041            } => Doc::line(
1042                Doc::text(format!(
1043                    "mem_copy_bytes {}, {}, {}",
1044                    namer.name(context, dst_val_ptr),
1045                    namer.name(context, src_val_ptr),
1046                    byte_len,
1047                ))
1048                .append(md_namer.md_idx_to_doc(context, metadata)),
1049            ),
1050            InstOp::MemCopyVal {
1051                dst_val_ptr,
1052                src_val_ptr,
1053            } => Doc::line(
1054                Doc::text(format!(
1055                    "mem_copy_val {}, {}",
1056                    namer.name(context, dst_val_ptr),
1057                    namer.name(context, src_val_ptr),
1058                ))
1059                .append(md_namer.md_idx_to_doc(context, metadata)),
1060            ),
1061            InstOp::Nop => Doc::line(
1062                Doc::text(format!("{} = nop", namer.name(context, ins_value)))
1063                    .append(md_namer.md_idx_to_doc(context, metadata)),
1064            ),
1065            InstOp::PtrToInt(value, ty) => maybe_constant_to_doc(context, md_namer, namer, value)
1066                .append(Doc::line(
1067                    Doc::text(format!(
1068                        "{} = ptr_to_int {} to {}",
1069                        namer.name(context, ins_value),
1070                        namer.name(context, value),
1071                        ty.as_string(context),
1072                    ))
1073                    .append(md_namer.md_idx_to_doc(context, metadata)),
1074                )),
1075            InstOp::Ret(v, t) => {
1076                maybe_constant_to_doc(context, md_namer, namer, v).append(Doc::line(
1077                    Doc::text(format!(
1078                        "ret {} {}",
1079                        t.as_string(context),
1080                        namer.name(context, v),
1081                    ))
1082                    .append(md_namer.md_idx_to_doc(context, metadata)),
1083                ))
1084            }
1085            InstOp::Store {
1086                dst_val_ptr,
1087                stored_val,
1088            } => maybe_constant_to_doc(context, md_namer, namer, stored_val).append(Doc::line(
1089                Doc::text(format!(
1090                    "store {} to {}",
1091                    namer.name(context, stored_val),
1092                    namer.name(context, dst_val_ptr),
1093                ))
1094                .append(md_namer.md_idx_to_doc(context, metadata)),
1095            )),
1096        }
1097    } else {
1098        unreachable!("Unexpected non instruction for block contents.")
1099    }
1100}
1101
1102fn asm_block_to_doc(
1103    context: &Context,
1104    md_namer: &mut MetadataNamer,
1105    namer: &mut Namer,
1106    ins_value: &Value,
1107    asm: &AsmBlock,
1108    args: &[AsmArg],
1109    metadata: &Option<MetadataIndex>,
1110) -> Doc {
1111    let AsmBlock {
1112        body,
1113        return_type,
1114        return_name,
1115        ..
1116    } = &asm;
1117    args.iter()
1118        .fold(
1119            Doc::Empty,
1120            |doc, AsmArg { initializer, .. }| match initializer {
1121                Some(init_val) if init_val.is_constant(context) => {
1122                    doc.append(maybe_constant_to_doc(context, md_namer, namer, init_val))
1123                }
1124                _otherwise => doc,
1125            },
1126        )
1127        .append(Doc::line(
1128            Doc::text(format!("{} = asm", namer.name(context, ins_value)))
1129                .append(Doc::in_parens_comma_sep(
1130                    args.iter()
1131                        .map(|AsmArg { name, initializer }| {
1132                            Doc::text(name.as_str()).append(match initializer {
1133                                Some(init_val) => {
1134                                    Doc::text(format!(": {}", namer.name(context, init_val)))
1135                                }
1136                                None => Doc::Empty,
1137                            })
1138                        })
1139                        .collect(),
1140                ))
1141                .append(
1142                    Doc::text(format!(
1143                        " -> {}{}",
1144                        return_type.as_string(context),
1145                        return_name
1146                            .as_ref()
1147                            .map_or("".to_string(), |rn| format!(" {rn}"))
1148                    ))
1149                    .append(md_namer.md_idx_to_doc(context, metadata)),
1150                )
1151                .append(Doc::text(" {")),
1152        ))
1153        .append(Doc::indent(
1154            4,
1155            Doc::List(
1156                body.iter()
1157                    .map(
1158                        |AsmInstruction {
1159                             op_name: name,
1160                             args,
1161                             immediate,
1162                             metadata,
1163                         }| {
1164                            Doc::line(
1165                                Doc::text(format!("{:6} ", name.as_str())).append(
1166                                    Doc::list_sep(
1167                                        args.iter().map(|arg| Doc::text(arg.as_str())).collect(),
1168                                        Doc::text(" "),
1169                                    )
1170                                    .append(match immediate {
1171                                        Some(imm_str) => Doc::text(format!(" {imm_str}")),
1172                                        None => Doc::Empty,
1173                                    })
1174                                    .append(md_namer.md_idx_to_doc(context, metadata)),
1175                                ),
1176                            )
1177                        },
1178                    )
1179                    .collect(),
1180            ),
1181        ))
1182        .append(Doc::text_line("}"))
1183}
1184
1185impl ConstantContent {
1186    fn as_lit_string(&self, context: &Context) -> String {
1187        match &self.value {
1188            ConstantValue::Undef => format!("{} undef", self.ty.as_string(context)),
1189            ConstantValue::Unit => "unit ()".into(),
1190            ConstantValue::Bool(b) => format!("bool {}", if *b { "true" } else { "false" }),
1191            ConstantValue::Uint(v) => format!("{} {}", self.ty.as_string(context), v),
1192            ConstantValue::U256(v) => {
1193                let bytes = v.to_be_bytes();
1194                format!(
1195                    "u256 0x{}",
1196                    bytes
1197                        .iter()
1198                        .map(|b| format!("{b:02x}"))
1199                        .collect::<Vec<String>>()
1200                        .concat()
1201                )
1202            }
1203            ConstantValue::B256(v) => {
1204                let bytes = v.to_be_bytes();
1205                format!(
1206                    "b256 0x{}",
1207                    bytes
1208                        .iter()
1209                        .map(|b| format!("{b:02x}"))
1210                        .collect::<Vec<String>>()
1211                        .concat()
1212                )
1213            }
1214            ConstantValue::String(bs) => format!(
1215                "{} \"{}\"",
1216                self.ty.as_string(context),
1217                bs.iter()
1218                    .map(
1219                        |b| if b.is_ascii() && !b.is_ascii_control() && *b != b'\\' && *b != b'"' {
1220                            format!("{}", *b as char)
1221                        } else {
1222                            format!("\\x{b:02x}")
1223                        }
1224                    )
1225                    .collect::<Vec<_>>()
1226                    .join("")
1227            ),
1228            ConstantValue::Array(elems) => format!(
1229                "{} [{}]",
1230                self.ty.as_string(context),
1231                elems
1232                    .iter()
1233                    .map(|elem| elem.as_lit_string(context))
1234                    .collect::<Vec<String>>()
1235                    .join(", ")
1236            ),
1237            ConstantValue::Slice(elems) => format!(
1238                "__slice[{}] [{}]",
1239                self.ty.as_string(context),
1240                elems
1241                    .iter()
1242                    .map(|elem| elem.as_lit_string(context))
1243                    .collect::<Vec<String>>()
1244                    .join(", ")
1245            ),
1246            ConstantValue::Struct(fields) => format!(
1247                "{} {{ {} }}",
1248                self.ty.as_string(context),
1249                fields
1250                    .iter()
1251                    .map(|field| field.as_lit_string(context))
1252                    .collect::<Vec<String>>()
1253                    .join(", ")
1254            ),
1255            ConstantValue::Reference(constant) => format!("&({})", constant.as_lit_string(context)),
1256            ConstantValue::RawUntypedSlice(bytes) => {
1257                format!(
1258                    "{} 0x{}",
1259                    self.ty.as_string(context),
1260                    bytes
1261                        .iter()
1262                        .map(|b| format!("{b:02x}"))
1263                        .collect::<Vec<String>>()
1264                        .concat()
1265                )
1266            }
1267        }
1268    }
1269}
1270
1271struct Namer {
1272    function: Function,
1273    names: HashMap<Value, String>,
1274    next_value_idx: u64,
1275}
1276
1277impl Namer {
1278    fn new(function: Function) -> Self {
1279        Namer {
1280            function,
1281            names: HashMap::new(),
1282            next_value_idx: 0,
1283        }
1284    }
1285
1286    fn name(&mut self, context: &Context, value: &Value) -> String {
1287        match &context.values[value.0].value {
1288            ValueDatum::Argument(_) => self
1289                .function
1290                .lookup_arg_name(context, value)
1291                .cloned()
1292                .unwrap_or_else(|| self.default_name(value)),
1293            ValueDatum::Constant(_) => self.default_name(value),
1294            ValueDatum::Instruction(_) => self.default_name(value),
1295        }
1296    }
1297
1298    fn default_name(&mut self, value: &Value) -> String {
1299        self.names.get(value).cloned().unwrap_or_else(|| {
1300            let new_name = format!("v{}", self.next_value_idx);
1301            self.next_value_idx += 1;
1302            self.names.insert(*value, new_name.clone());
1303            new_name
1304        })
1305    }
1306
1307    fn is_known(&self, value: &Value) -> bool {
1308        self.names.contains_key(value)
1309    }
1310}
1311
1312#[derive(Default)]
1313struct MetadataNamer {
1314    md_map: BTreeMap<MetadataIndex, u64>,
1315    next_md_idx: u64,
1316}
1317
1318impl MetadataNamer {
1319    fn values_sorted(&self) -> impl Iterator<Item = (u64, MetadataIndex)> {
1320        let mut items = self
1321            .md_map
1322            .clone()
1323            .into_iter()
1324            .map(|(a, b)| (b, a))
1325            .collect::<Vec<_>>();
1326        items.sort_unstable();
1327        items.into_iter()
1328    }
1329
1330    fn get(&self, md_idx: &MetadataIndex) -> Option<u64> {
1331        self.md_map.get(md_idx).copied()
1332    }
1333
1334    // This method is how we introduce 'valid' metadata to the namer, as only valid metadata are
1335    // printed at the end.  Since metadata are stored globally to the context there may be a bunch
1336    // in there which aren't relevant (e.g., library code).  Hopefully this will go away when the
1337    // Sway compiler becomes properly modular and eschews all the inlining it does.
1338    //
1339    // So, we insert a reference index into the namer whenever we see a new metadata index passed
1340    // here.  But we also need to recursively 'validate' any other metadata referred to, e.g., list
1341    // elements, struct members, etc. It's done in `add_md_idx()` below.
1342    fn md_idx_to_doc_no_comma(&mut self, context: &Context, md_idx: &Option<MetadataIndex>) -> Doc {
1343        md_idx
1344            .map(|md_idx| Doc::text(format!("!{}", self.add_md_idx(context, &md_idx))))
1345            .unwrap_or(Doc::Empty)
1346    }
1347
1348    fn md_idx_to_doc(&mut self, context: &Context, md_idx: &Option<MetadataIndex>) -> Doc {
1349        Doc::Comma.and(self.md_idx_to_doc_no_comma(context, md_idx))
1350    }
1351
1352    fn add_md_idx(&mut self, context: &Context, md_idx: &MetadataIndex) -> u64 {
1353        self.md_map.get(md_idx).copied().unwrap_or_else(|| {
1354            // Recurse for all sub-metadata here first to be sure they can be referenced later.
1355            self.add_md(context, &context.metadata[md_idx.0]);
1356
1357            // Create a new index mapping.
1358            let new_idx = self.next_md_idx;
1359            self.next_md_idx += 1;
1360            self.md_map.insert(*md_idx, new_idx);
1361            new_idx
1362        })
1363    }
1364
1365    fn add_md(&mut self, context: &Context, md: &Metadatum) {
1366        match md {
1367            Metadatum::Integer(_) | Metadatum::String(_) | Metadatum::SourceId(_) => (),
1368            Metadatum::Index(idx) => {
1369                let _ = self.add_md_idx(context, idx);
1370            }
1371            Metadatum::Struct(_tag, els) => {
1372                for el in els {
1373                    self.add_md(context, el);
1374                }
1375            }
1376            Metadatum::List(idcs) => {
1377                for idx in idcs {
1378                    self.add_md_idx(context, idx);
1379                }
1380            }
1381        }
1382    }
1383
1384    fn to_doc(&self, context: &Context) -> Doc {
1385        fn md_to_string(
1386            md_namer: &MetadataNamer,
1387            md: &Metadatum,
1388            source_engine: &SourceEngine,
1389        ) -> String {
1390            match md {
1391                Metadatum::Integer(i) => i.to_string(),
1392                Metadatum::Index(idx) => format!(
1393                    "!{}",
1394                    md_namer
1395                        .get(idx)
1396                        .unwrap_or_else(|| panic!("Metadata index ({idx:?}) not found in namer."))
1397                ),
1398                Metadatum::String(s) => format!("{s:?}"),
1399                Metadatum::SourceId(id) => {
1400                    let path = source_engine.get_path(id);
1401                    format!("{path:?}")
1402                }
1403                Metadatum::Struct(tag, els) => {
1404                    format!(
1405                        "{tag} {}",
1406                        els.iter()
1407                            .map(|el_md| md_to_string(md_namer, el_md, source_engine))
1408                            .collect::<Vec<_>>()
1409                            .join(" ")
1410                    )
1411                }
1412                Metadatum::List(idcs) => {
1413                    format!(
1414                        "({})",
1415                        idcs.iter()
1416                            .map(|idx| format!(
1417                                "!{}",
1418                                md_namer.get(idx).unwrap_or_else(|| panic!(
1419                                    "Metadata list index ({idx:?}) not found in namer."
1420                                ))
1421                            ))
1422                            .collect::<Vec<_>>()
1423                            .join(" ")
1424                    )
1425                }
1426            }
1427        }
1428
1429        let md_lines = self
1430            .values_sorted()
1431            .map(|(ref_idx, md_idx)| {
1432                Doc::text_line(format!(
1433                    "!{ref_idx} = {}",
1434                    md_to_string(self, &context.metadata[md_idx.0], context.source_engine)
1435                ))
1436            })
1437            .collect::<Vec<_>>();
1438
1439        // We want to add an empty line only when there are metadata.
1440        if md_lines.is_empty() {
1441            Doc::Empty
1442        } else {
1443            Doc::line(Doc::Empty).append(Doc::List(md_lines))
1444        }
1445    }
1446}
1447
1448/// There will be a much more efficient way to do this, but for now this will do.
1449fn build_doc(doc: Doc, indent: i64) -> String {
1450    match doc {
1451        Doc::Empty => "".into(),
1452        Doc::Space => " ".into(),
1453        Doc::Comma => ", ".into(),
1454
1455        Doc::Text(t) => t,
1456        Doc::Line(d) => {
1457            if matches!(*d, Doc::Empty) {
1458                "\n".into()
1459            } else {
1460                format!("{}{}\n", " ".repeat(indent as usize), build_doc(*d, indent))
1461            }
1462        }
1463
1464        Doc::Pair(l, r) => [build_doc(*l, indent), build_doc(*r, indent)].concat(),
1465
1466        Doc::List(v) => v
1467            .into_iter()
1468            .map(|d| build_doc(d, indent))
1469            .collect::<Vec<String>>()
1470            .concat(),
1471        Doc::ListSep(v, s) => v
1472            .into_iter()
1473            .filter_map(|d| match &d {
1474                Doc::Empty => None,
1475                Doc::List(vs) => {
1476                    if vs.is_empty() {
1477                        None
1478                    } else {
1479                        Some(build_doc(d, indent))
1480                    }
1481                }
1482                _ => Some(build_doc(d, indent)),
1483            })
1484            .collect::<Vec<String>>()
1485            .join(&build_doc(*s, indent)),
1486
1487        Doc::Parens(d) => format!("({})", build_doc(*d, indent)),
1488
1489        Doc::Indent(n, d) => build_doc(*d, indent + n),
1490    }
1491}