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