1use 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
90pub 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
123pub 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
144pub 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
160pub 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
183pub 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 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 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 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, ¤t_value);
528 (map_doc)(¤t_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 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 {
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 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 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 self.add_md(context, &context.metadata[md_idx.0]);
1471
1472 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 if md_lines.is_empty() {
1556 Doc::Empty
1557 } else {
1558 Doc::line(Doc::Empty).append(Doc::List(md_lines))
1559 }
1560 }
1561}
1562
1563fn 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}