calyx_ir/
printer.rs

1//! Implements a formatter for the in-memory representation of Components.
2//! The printing operation clones inner nodes and doesn't perform any mutation
3//! to the Component.
4use crate::{self as ir, RRC};
5use calyx_frontend::{PrimitiveInfo, source_info::SourceInfoTable};
6use calyx_utils::float;
7use itertools::Itertools;
8use std::io;
9use std::path::Path;
10use std::rc::Rc;
11
12/// Printer for the IR.
13pub struct Printer;
14
15impl Printer {
16    /// Format attributes of the form `@static(1)`.
17    /// Returns the empty string if the `attrs` is empty.
18    pub fn format_at_attributes(attrs: &ir::Attributes) -> String {
19        let mut buf = attrs.to_string_with(
20            " ",
21            |name, val| {
22                if val == 1 {
23                    format!("@{name}")
24                } else {
25                    format!("@{name}({val})")
26                }
27            },
28            |name, vals| format!("@{}{{{}}}", name, vals.iter().join(", ")),
29        );
30        if !attrs.is_empty() {
31            buf.push(' ');
32        }
33        buf
34    }
35
36    /// Format attributes of the form `<"static"=1>`.
37    /// Returns the empty string if the `attrs` is empty.
38    pub fn format_attributes(attrs: &ir::Attributes) -> String {
39        if attrs.is_empty() {
40            "".to_string()
41        } else {
42            format!(
43                "<{}>",
44                attrs.to_string_with(
45                    ", ",
46                    |name, val| { format!("\"{name}\"={val}") },
47                    |name, vals| {
48                        format!("\"{}\"={{{}}}", name, vals.iter().join(", "))
49                    },
50                )
51            )
52        }
53    }
54
55    /// Formats port definitions in signatures
56    pub fn format_ports(ports: &[RRC<ir::Port>]) -> String {
57        ports
58            .iter()
59            .map(|p| {
60                format!(
61                    "{}{}: {}",
62                    Self::format_at_attributes(&p.borrow().attributes),
63                    p.borrow().name.id,
64                    p.borrow().width
65                )
66            })
67            .collect::<Vec<_>>()
68            .join(", ")
69    }
70
71    pub fn format_port_def<W: std::fmt::Display>(
72        port_defs: &[&ir::PortDef<W>],
73    ) -> String {
74        port_defs
75            .iter()
76            .map(|pd| {
77                format!(
78                    "{}{}: {}",
79                    Self::format_at_attributes(&pd.attributes),
80                    pd.name(),
81                    pd.width
82                )
83            })
84            .collect_vec()
85            .join(", ")
86    }
87
88    /// Prints out the program context.
89    /// If `skip_primitives` is true, the printer will skip printing primitives defined outside the source file.
90    pub fn write_context<F: io::Write>(
91        ctx: &ir::Context,
92        skip_primitives: bool,
93        f: &mut F,
94    ) -> io::Result<()> {
95        for prim_info in ctx.lib.prim_infos() {
96            if skip_primitives && !prim_info.is_source() {
97                continue;
98            }
99            match prim_info {
100                PrimitiveInfo::Extern {
101                    path, primitives, ..
102                } => {
103                    ir::Printer::write_externs(
104                        (path, primitives.into_iter().map(|(_, v)| v)),
105                        f,
106                    )?;
107                }
108                PrimitiveInfo::Inline { primitive, .. } => {
109                    ir::Printer::write_primitive(primitive, 0, f)?;
110                }
111            }
112        }
113
114        for comp in &ctx.components {
115            ir::Printer::write_component(comp, f)?;
116            writeln!(f)?
117        }
118        write!(f, "{}", ir::Printer::format_metadata(&ctx.metadata))?;
119
120        Printer::write_source_info_table(f, &ctx.source_info_table)
121    }
122
123    /// Formats and writes extern statements.
124    pub fn write_externs<'a, F, I>(
125        (path, prims): (&Path, I),
126        f: &mut F,
127    ) -> io::Result<()>
128    where
129        F: io::Write,
130        I: Iterator<Item = &'a ir::Primitive>,
131    {
132        writeln!(f, "extern \"{}\" {{", path.to_string_lossy())?;
133        for prim in prims {
134            Self::write_primitive(prim, 2, f)?;
135        }
136        writeln!(f, "}}")
137    }
138
139    pub fn write_primitive<F: io::Write>(
140        prim: &ir::Primitive,
141        indent: usize,
142        f: &mut F,
143    ) -> io::Result<()> {
144        write!(f, "{}", " ".repeat(indent))?;
145        if prim.is_comb {
146            write!(f, "comb ")?;
147        }
148        if let Some(latency_val) = prim.latency {
149            write!(f, "static<{latency_val}> ")?;
150        }
151        write!(
152            f,
153            "primitive {}{}",
154            prim.name,
155            Self::format_attributes(&prim.attributes)
156        )?;
157        if !prim.params.is_empty() {
158            write!(
159                f,
160                "[{}]",
161                prim.params
162                    .iter()
163                    .map(|p| p.to_string())
164                    .collect_vec()
165                    .join(", ")
166            )?
167        }
168        let (mut inputs, mut outputs) = (vec![], vec![]);
169        for pd in &prim.signature {
170            if pd.direction == ir::Direction::Input {
171                inputs.push(pd)
172            } else {
173                outputs.push(pd)
174            }
175        }
176        write!(
177            f,
178            "({}) -> ({})",
179            Self::format_port_def(&inputs),
180            Self::format_port_def(&outputs)
181        )?;
182        if let Some(b) = prim.body.as_ref() {
183            writeln!(f, " {{")?;
184            writeln!(f, "{:indent$}{b}", "", indent = indent + 2)?;
185            writeln!(f, "}}")
186        } else {
187            writeln!(f, ";")
188        }
189    }
190
191    /// Formats and writes the Component to the formatter.
192    pub fn write_component<F: io::Write>(
193        comp: &ir::Component,
194        f: &mut F,
195    ) -> io::Result<()> {
196        let sig = comp.signature.borrow();
197        let (inputs, outputs): (Vec<_>, Vec<_>) =
198            sig.ports.iter().map(Rc::clone).partition(|p| {
199                // Cell signature stores the ports in reversed direction.
200                matches!(p.borrow().direction, ir::Direction::Output)
201            });
202
203        let pre = if comp.is_comb {
204            "comb ".to_string()
205        } else if comp.latency.is_some() {
206            format!("static<{}> ", comp.latency.unwrap())
207        } else {
208            "".to_string()
209        };
210
211        writeln!(
212            f,
213            "{}component {}{}({}) -> ({}) {{",
214            pre,
215            comp.name.id,
216            Self::format_attributes(&comp.attributes),
217            Self::format_ports(&inputs),
218            Self::format_ports(&outputs),
219        )?;
220
221        // Add the cells
222        write!(f, "  cells {{")?;
223        if !comp.cells.is_empty() {
224            writeln!(f)?;
225        }
226        for cell in comp.cells.iter() {
227            Self::write_cell(&cell.borrow(), 4, f)?;
228        }
229        if !comp.cells.is_empty() {
230            writeln!(f, "  }}")?;
231        } else {
232            writeln!(f, "}}")?;
233        }
234
235        // Add the wires
236        let empty_wires = comp.groups.is_empty()
237            && comp.static_groups.is_empty()
238            && comp.comb_groups.is_empty()
239            && comp.continuous_assignments.is_empty();
240        write!(f, "  wires {{")?;
241        if !empty_wires {
242            writeln!(f)?;
243        }
244        for group in comp.get_groups().iter() {
245            Self::write_group(&group.borrow(), 4, f)?;
246            writeln!(f)?;
247        }
248        for group in comp.get_static_groups().iter() {
249            Self::write_static_group(&group.borrow(), 4, f)?;
250            writeln!(f)?;
251        }
252        for fsm in comp.get_fsms().iter() {
253            Self::write_fsm(&fsm.borrow(), 4, f)?;
254            writeln!(f)?;
255        }
256        for comb_group in comp.comb_groups.iter() {
257            Self::write_comb_group(&comb_group.borrow(), 4, f)?;
258            writeln!(f)?;
259        }
260        // Write the continuous assignments
261        for assign in &comp.continuous_assignments {
262            Self::write_assignment(assign, 4, f)?;
263            writeln!(f)?;
264        }
265        if !empty_wires {
266            writeln!(f, "  }}")?;
267        } else {
268            writeln!(f, "}}")?;
269        }
270
271        // Add the control program.
272        // Since the syntax doesn't allow combinational components to have a control block, the attributes will always be empty
273        if !comp.is_comb {
274            let con = &*comp.control.borrow();
275            match con {
276                ir::Control::Empty(ir::Empty { attributes })
277                    if attributes.is_empty() =>
278                {
279                    writeln!(f, "  control {{}}")?;
280                }
281                _ => {
282                    writeln!(f, "  control {{")?;
283                    Self::write_control(&comp.control.borrow(), 4, f)?;
284                    writeln!(f, "  }}")?;
285                }
286            }
287        }
288
289        write!(f, "}}")
290    }
291
292    pub fn write_float_const<F: io::Write>(
293        cell: &ir::Cell,
294        indent_level: usize,
295        f: &mut F,
296    ) -> io::Result<()> {
297        let ir::CellType::Primitive {
298            name: prim,
299            param_binding,
300            ..
301        } = &cell.prototype
302        else {
303            unreachable!("Expected std_float_const cell")
304        };
305
306        let (rep, width, val) =
307            (param_binding[0].1, param_binding[1].1, param_binding[2].1);
308
309        let fl = match float::emit(val, width) {
310            Ok(fl) => fl,
311            Err(e) => {
312                panic!("Error emitting float constant: {e:?}")
313            }
314        };
315
316        write!(f, "{}", " ".repeat(indent_level))?;
317        write!(f, "{}", Self::format_at_attributes(&cell.attributes))?;
318        if cell.is_reference() {
319            write!(f, "ref ")?;
320        }
321        writeln!(f, "{} = {prim}({rep}, {width}, {fl});", cell.name().id)?;
322        Ok(())
323    }
324
325    /// Format and write a cell.
326    pub fn write_cell<F: io::Write>(
327        cell: &ir::Cell,
328        indent_level: usize,
329        f: &mut F,
330    ) -> io::Result<()> {
331        match &cell.prototype {
332            ir::CellType::Primitive {
333                name,
334                param_binding,
335                ..
336            } => {
337                if name == "std_float_const" {
338                    return Self::write_float_const(cell, indent_level, f);
339                }
340
341                write!(f, "{}", " ".repeat(indent_level))?;
342                write!(f, "{}", Self::format_at_attributes(&cell.attributes))?;
343                if cell.is_reference() {
344                    write!(f, "ref ")?
345                }
346                write!(f, "{} = ", cell.name().id)?;
347                writeln!(
348                    f,
349                    "{}({});",
350                    name.id,
351                    param_binding
352                        .iter()
353                        .map(|(_, v)| v.to_string())
354                        .collect::<Vec<_>>()
355                        .join(", ")
356                )
357            }
358            ir::CellType::Component { name, .. } => {
359                write!(f, "{}", " ".repeat(indent_level))?;
360                write!(f, "{}", Self::format_at_attributes(&cell.attributes))?;
361                if cell.is_reference() {
362                    write!(f, "ref ")?
363                }
364                writeln!(f, "{} = {}();", cell.name().id, name)
365            }
366            ir::CellType::Constant { .. } => Ok(()),
367            _ => unimplemented!(),
368        }
369    }
370
371    /// Format and write an assignment.
372    pub fn write_assignment<F: io::Write, T: Clone + ToString + Eq>(
373        assign: &ir::Assignment<T>,
374        indent_level: usize,
375        f: &mut F,
376    ) -> io::Result<()> {
377        write!(f, "{}", " ".repeat(indent_level))?;
378        write!(f, "{}", Self::format_at_attributes(&assign.attributes))?;
379        write!(f, "{} = ", Self::port_to_str(&assign.dst.borrow()))?;
380        if !matches!(&*assign.guard, ir::Guard::True) {
381            write!(f, "{} ? ", Self::guard_str(&assign.guard.clone()))?;
382        }
383        write!(f, "{};", Self::port_to_str(&assign.src.borrow()))
384    }
385
386    /// Convinience method to get string representation of [ir::Assignment].
387    pub fn assignment_to_str<T>(assign: &ir::Assignment<T>) -> String
388    where
389        T: ToString + Clone + Eq,
390    {
391        let mut buf = Vec::new();
392        Self::write_assignment(assign, 0, &mut buf).ok();
393        String::from_utf8_lossy(buf.as_slice()).to_string()
394    }
395
396    /// Convinience method to get string representation of [ir::Control].
397    pub fn control_to_str(assign: &ir::Control) -> String {
398        let mut buf = Vec::new();
399        Self::write_control(assign, 0, &mut buf).ok();
400        String::from_utf8_lossy(buf.as_slice()).to_string()
401    }
402
403    /// Format and write a combinational group.
404    pub fn write_comb_group<F: io::Write>(
405        group: &ir::CombGroup,
406        indent_level: usize,
407        f: &mut F,
408    ) -> io::Result<()> {
409        write!(f, "{}", " ".repeat(indent_level))?;
410        write!(f, "comb group {}", group.name().id)?;
411        if !group.attributes.is_empty() {
412            write!(f, "{}", Self::format_attributes(&group.attributes))?;
413        }
414        writeln!(f, " {{")?;
415
416        for assign in &group.assignments {
417            Self::write_assignment(assign, indent_level + 2, f)?;
418            writeln!(f)?;
419        }
420        write!(f, "{}}}", " ".repeat(indent_level))
421    }
422
423    /// Format and write a group.
424    pub fn write_group<F: io::Write>(
425        group: &ir::Group,
426        indent_level: usize,
427        f: &mut F,
428    ) -> io::Result<()> {
429        write!(f, "{}", " ".repeat(indent_level))?;
430        write!(f, "group {}", group.name().id)?;
431        if !group.attributes.is_empty() {
432            write!(f, "{}", Self::format_attributes(&group.attributes))?;
433        }
434        writeln!(f, " {{")?;
435
436        for assign in &group.assignments {
437            Self::write_assignment(assign, indent_level + 2, f)?;
438            writeln!(f)?;
439        }
440        write!(f, "{}}}", " ".repeat(indent_level))
441    }
442
443    /// Format and write a static group.
444    pub fn write_static_group<F: io::Write>(
445        group: &ir::StaticGroup,
446        indent_level: usize,
447        f: &mut F,
448    ) -> io::Result<()> {
449        write!(f, "{}", " ".repeat(indent_level))?;
450        write!(
451            f,
452            "static<{}> group {}",
453            group.get_latency(),
454            group.name().id,
455        )?;
456        if !group.attributes.is_empty() {
457            write!(f, "{}", Self::format_attributes(&group.attributes))?;
458        }
459        writeln!(f, " {{")?;
460
461        for assign in &group.assignments {
462            Self::write_assignment(assign, indent_level + 2, f)?;
463            writeln!(f)?;
464        }
465        write!(f, "{}}}", " ".repeat(indent_level))
466    }
467
468    /// Format and write an FSM
469    pub fn write_fsm<F: io::Write>(
470        fsm: &ir::FSM,
471        indent_level: usize,
472        f: &mut F,
473    ) -> io::Result<()> {
474        write!(f, "{}", " ".repeat(indent_level))?;
475        write!(f, "fsm {}", fsm.name().id)?;
476        if !fsm.attributes.is_empty() {
477            write!(f, "{}", Self::format_attributes(&fsm.attributes))?;
478        }
479        writeln!(f, " {{")?;
480        for (i, (assigns, trans)) in
481            fsm.assignments.iter().zip(&fsm.transitions).enumerate()
482        {
483            // WRITE ASSIGNMENTS
484            write!(f, "{}", " ".repeat(indent_level + 2))?;
485            write!(f, "{i} : ")?;
486            match assigns.is_empty() {
487                true => {
488                    // skip directly to transitions section
489                    write!(f, "{{")?;
490                    write!(f, "}} ")?;
491                    write!(f, "=> ")?;
492                }
493                false => {
494                    writeln!(f, "{{")?;
495                    for assign in assigns {
496                        Self::write_assignment(assign, indent_level + 4, f)?;
497                        writeln!(f)?;
498                    }
499                    write!(f, "{}", " ".repeat(indent_level + 2))?;
500                    write!(f, "}} => ")?;
501                }
502            }
503
504            // WRITE TRANSITIONS
505            match trans {
506                ir::Transition::Unconditional(s) => {
507                    writeln!(f, "{s},")?;
508                }
509                ir::Transition::Conditional(cond_dsts) => {
510                    writeln!(f, "{{")?;
511                    for (i, (g, dst)) in cond_dsts.iter().enumerate() {
512                        write!(f, "{}", " ".repeat(indent_level + 4))?;
513                        if i == cond_dsts.len() - 1 {
514                            writeln!(f, "default -> {dst},")?;
515                        } else {
516                            writeln!(f, "{} -> {},", Self::guard_str(g), dst)?;
517                        }
518                    }
519                    write!(f, "{}", " ".repeat(indent_level + 2))?;
520                    writeln!(f, "}},")?;
521                }
522            }
523        }
524        write!(f, "{}}}", " ".repeat(indent_level))
525    }
526
527    /// Format and write a static control program
528    pub fn write_static_control<F: io::Write>(
529        scontrol: &ir::StaticControl,
530        indent_level: usize,
531        f: &mut F,
532    ) -> io::Result<()> {
533        write!(f, "{}", " ".repeat(indent_level))?;
534        match scontrol {
535            ir::StaticControl::Enable(ir::StaticEnable {
536                group,
537                attributes,
538            }) => {
539                write!(f, "{}", Self::format_at_attributes(attributes))?;
540                writeln!(f, "{};", group.borrow().name().id)
541            }
542            ir::StaticControl::Repeat(ir::StaticRepeat {
543                num_repeats,
544                attributes,
545                body,
546                ..
547            }) => {
548                write!(f, "{}", Self::format_at_attributes(attributes))?;
549                write!(f, "static repeat {num_repeats} ")?;
550                writeln!(f, "{{")?;
551                Self::write_static_control(body, indent_level + 2, f)?;
552                writeln!(f, "{}}}", " ".repeat(indent_level))
553            }
554            ir::StaticControl::Seq(ir::StaticSeq {
555                stmts,
556                attributes,
557                latency,
558            }) => {
559                write!(f, "{}", Self::format_at_attributes(attributes))?;
560                writeln!(f, "static<{latency}> seq  {{")?;
561                for stmt in stmts {
562                    Self::write_static_control(stmt, indent_level + 2, f)?;
563                }
564                writeln!(f, "{}}}", " ".repeat(indent_level))
565            }
566            ir::StaticControl::Par(ir::StaticPar {
567                stmts,
568                attributes,
569                latency,
570            }) => {
571                write!(f, "{}", Self::format_at_attributes(attributes))?;
572                writeln!(f, "static<{latency}> par {{")?;
573                for stmt in stmts {
574                    Self::write_static_control(stmt, indent_level + 2, f)?;
575                }
576                writeln!(f, "{}}}", " ".repeat(indent_level))
577            }
578            ir::StaticControl::Empty(ir::Empty { attributes }) => {
579                if !attributes.is_empty() {
580                    writeln!(f, "{};", Self::format_at_attributes(attributes))
581                } else {
582                    writeln!(f)
583                }
584            }
585            ir::StaticControl::If(ir::StaticIf {
586                port,
587                latency,
588                tbranch,
589                fbranch,
590                attributes,
591            }) => {
592                write!(f, "{}", Self::format_at_attributes(attributes))?;
593                write!(
594                    f,
595                    "static<{}> if  {} ",
596                    latency,
597                    Self::port_to_str(&port.borrow()),
598                )?;
599                writeln!(f, "{{")?;
600                Self::write_static_control(tbranch, indent_level + 2, f)?;
601                write!(f, "{}}}", " ".repeat(indent_level))?;
602                if let ir::StaticControl::Empty(_) = **fbranch {
603                    writeln!(f)
604                } else {
605                    writeln!(f, " else {{")?;
606                    Self::write_static_control(fbranch, indent_level + 2, f)?;
607                    writeln!(f, "{}}}", " ".repeat(indent_level))
608                }
609            }
610            ir::StaticControl::Invoke(ir::StaticInvoke {
611                comp,
612                latency,
613                inputs,
614                outputs,
615                attributes,
616                ref_cells,
617                comb_group,
618            }) => {
619                write!(f, "{}", Self::format_at_attributes(attributes))?;
620                write!(
621                    f,
622                    "static<{}> invoke {}",
623                    latency,
624                    comp.borrow().name()
625                )?;
626                if !ref_cells.is_empty() {
627                    write!(f, "[")?;
628                    for (i, (outcell, incell)) in ref_cells.iter().enumerate() {
629                        write!(
630                            f,
631                            "{}{} = {}",
632                            if i == 0 { "" } else { "," },
633                            outcell,
634                            incell.borrow().name()
635                        )?
636                    }
637                    write!(f, "]")?;
638                }
639                write!(f, "(")?;
640                for (i, (arg, port)) in inputs.iter().enumerate() {
641                    write!(
642                        f,
643                        "{}\n{}{} = {}",
644                        if i == 0 { "" } else { "," },
645                        " ".repeat(indent_level + 2),
646                        arg,
647                        Self::port_to_str(&port.borrow())
648                    )?;
649                }
650                if inputs.is_empty() {
651                    write!(f, ")(")?;
652                } else {
653                    write!(f, "\n{})(", " ".repeat(indent_level))?;
654                }
655                for (i, (arg, port)) in outputs.iter().enumerate() {
656                    write!(
657                        f,
658                        "{}\n{}{} = {}",
659                        if i == 0 { "" } else { "," },
660                        " ".repeat(indent_level + 2),
661                        arg,
662                        Self::port_to_str(&port.borrow())
663                    )?;
664                }
665                if outputs.is_empty() {
666                    write!(f, ")")?;
667                } else {
668                    write!(f, "\n{})", " ".repeat(indent_level))?;
669                }
670                if let Some(group) = comb_group {
671                    write!(f, " with {}", group.borrow().name)?;
672                }
673                writeln!(f, ";")
674            }
675        }
676    }
677
678    /// Format and write a control program
679    pub fn write_control<F: io::Write>(
680        control: &ir::Control,
681        indent_level: usize,
682        f: &mut F,
683    ) -> io::Result<()> {
684        // write_static_control will indent already so we don't want to indent twice
685        if !matches!(control, ir::Control::Static(_)) {
686            write!(f, "{}", " ".repeat(indent_level))?;
687        }
688        match control {
689            ir::Control::Enable(ir::Enable { group, attributes }) => {
690                write!(f, "{}", Self::format_at_attributes(attributes))?;
691                writeln!(f, "{};", group.borrow().name().id)
692            }
693            ir::Control::FSMEnable(ir::FSMEnable { fsm, attributes }) => {
694                write!(f, "{}", Self::format_at_attributes(attributes))?;
695                writeln!(f, "{};", fsm.borrow().name().id)
696            }
697            ir::Control::Invoke(ir::Invoke {
698                comp,
699                inputs,
700                outputs,
701                attributes,
702                comb_group,
703                ref_cells,
704            }) => {
705                if !attributes.is_empty() {
706                    write!(f, "{}", Self::format_at_attributes(attributes))?
707                }
708                write!(f, "invoke {}", comp.borrow().name())?;
709                if !ref_cells.is_empty() {
710                    write!(f, "[")?;
711                    for (i, (outcell, incell)) in ref_cells.iter().enumerate() {
712                        write!(
713                            f,
714                            "{}{} = {}",
715                            if i == 0 { "" } else { "," },
716                            outcell,
717                            incell.borrow().name()
718                        )?
719                    }
720                    write!(f, "]")?;
721                }
722                write!(f, "(")?;
723                for (i, (arg, port)) in inputs.iter().enumerate() {
724                    write!(
725                        f,
726                        "{}\n{}{} = {}",
727                        if i == 0 { "" } else { "," },
728                        " ".repeat(indent_level + 2),
729                        arg,
730                        Self::port_to_str(&port.borrow())
731                    )?;
732                }
733                if inputs.is_empty() {
734                    write!(f, ")(")?;
735                } else {
736                    write!(f, "\n{})(", " ".repeat(indent_level))?;
737                }
738                for (i, (arg, port)) in outputs.iter().enumerate() {
739                    write!(
740                        f,
741                        "{}\n{}{} = {}",
742                        if i == 0 { "" } else { "," },
743                        " ".repeat(indent_level + 2),
744                        arg,
745                        Self::port_to_str(&port.borrow())
746                    )?;
747                }
748                if outputs.is_empty() {
749                    write!(f, ")")?;
750                } else {
751                    write!(f, "\n{})", " ".repeat(indent_level))?;
752                }
753                if let Some(group) = comb_group {
754                    writeln!(f, " with {};", group.borrow().name)
755                } else {
756                    writeln!(f, ";")
757                }
758            }
759            ir::Control::Seq(ir::Seq { stmts, attributes }) => {
760                write!(f, "{}", Self::format_at_attributes(attributes))?;
761                writeln!(f, "seq {{")?;
762                for stmt in stmts {
763                    Self::write_control(stmt, indent_level + 2, f)?;
764                }
765                writeln!(f, "{}}}", " ".repeat(indent_level))
766            }
767            ir::Control::Repeat(ir::Repeat {
768                num_repeats,
769                attributes,
770                body,
771                ..
772            }) => {
773                write!(f, "{}", Self::format_at_attributes(attributes))?;
774                write!(f, "repeat {num_repeats} ")?;
775                writeln!(f, "{{")?;
776                Self::write_control(body, indent_level + 2, f)?;
777                writeln!(f, "{}}}", " ".repeat(indent_level))
778            }
779            ir::Control::Par(ir::Par { stmts, attributes }) => {
780                write!(f, "{}", Self::format_at_attributes(attributes))?;
781                writeln!(f, "par {{")?;
782                for stmt in stmts {
783                    Self::write_control(stmt, indent_level + 2, f)?;
784                }
785                writeln!(f, "{}}}", " ".repeat(indent_level))
786            }
787            ir::Control::If(ir::If {
788                port,
789                cond,
790                tbranch,
791                fbranch,
792                attributes,
793            }) => {
794                write!(f, "{}", Self::format_at_attributes(attributes))?;
795                write!(f, "if {} ", Self::port_to_str(&port.borrow()),)?;
796                if let Some(c) = cond {
797                    write!(f, "with {} ", c.borrow().name.id)?;
798                }
799                writeln!(f, "{{")?;
800                Self::write_control(tbranch, indent_level + 2, f)?;
801                write!(f, "{}}}", " ".repeat(indent_level))?;
802                if let ir::Control::Empty(_) = **fbranch {
803                    writeln!(f)
804                } else {
805                    writeln!(f, " else {{")?;
806                    Self::write_control(fbranch, indent_level + 2, f)?;
807                    writeln!(f, "{}}}", " ".repeat(indent_level))
808                }
809            }
810            ir::Control::While(ir::While {
811                port,
812                cond,
813                body,
814                attributes,
815            }) => {
816                write!(f, "{}", Self::format_at_attributes(attributes))?;
817                write!(f, "while {} ", Self::port_to_str(&port.borrow()),)?;
818                if let Some(c) = cond {
819                    write!(f, "with {} ", c.borrow().name.id)?;
820                }
821                writeln!(f, "{{")?;
822                Self::write_control(body, indent_level + 2, f)?;
823                writeln!(f, "{}}}", " ".repeat(indent_level))
824            }
825            ir::Control::Empty(ir::Empty { attributes }) => {
826                if !attributes.is_empty() {
827                    writeln!(f, "{};", Self::format_at_attributes(attributes))
828                } else {
829                    writeln!(f)
830                }
831            }
832            ir::Control::Static(sc) => {
833                Self::write_static_control(sc, indent_level, f)
834            }
835        }
836    }
837
838    /// Generate a String-based representation for a guard.
839    pub fn guard_str<T>(guard: &ir::Guard<T>) -> String
840    where
841        T: Eq + ToString,
842    {
843        match &guard {
844            ir::Guard::And(l, r) | ir::Guard::Or(l, r) => {
845                let left = if &**l > guard {
846                    format!("({})", Self::guard_str(l))
847                } else {
848                    Self::guard_str(l)
849                };
850                let right = if &**r > guard {
851                    format!("({})", Self::guard_str(r))
852                } else {
853                    Self::guard_str(r)
854                };
855                format!("{} {} {}", left, &guard.op_str(), right)
856            }
857            ir::Guard::CompOp(_, l, r) => {
858                format!(
859                    "{} {} {}",
860                    Self::port_to_str(&l.borrow()),
861                    &guard.op_str(),
862                    Self::port_to_str(&r.borrow())
863                )
864            }
865            ir::Guard::Not(g) => {
866                let s = if &**g > guard {
867                    format!("({})", Self::guard_str(g))
868                } else {
869                    Self::guard_str(g)
870                };
871                format!("!{s}")
872            }
873            ir::Guard::Port(port_ref) => Self::port_to_str(&port_ref.borrow()),
874            ir::Guard::True => "1'b1".to_string(),
875            ir::Guard::Info(i) => i.to_string(),
876        }
877    }
878
879    /// Get the port access expression.
880    pub fn port_to_str(port: &ir::Port) -> String {
881        match &port.parent {
882            ir::PortParent::Cell(cell_wref) => {
883                let cell_ref =
884                    cell_wref.internal.upgrade().unwrap_or_else(|| {
885                        panic!(
886                            "Malformed AST: No reference to Cell for port `{}'",
887                            port.name
888                        )
889                    });
890                let cell = cell_ref.borrow();
891                match cell.prototype {
892                    ir::CellType::Constant { val, width } => {
893                        format!("{width}'d{val}")
894                    }
895                    ir::CellType::ThisComponent => port.name.to_string(),
896                    _ => format!("{}.{}", cell.name().id, port.name.id),
897                }
898            }
899            ir::PortParent::Group(group_wref) => format!(
900                "{}[{}]",
901                group_wref
902                    .internal
903                    .upgrade()
904                    .unwrap_or_else(|| panic!(
905                        "Malformed AST: No reference to Group for port `{port:#?}'"
906                    ))
907                    .borrow()
908                    .name()
909                    .id,
910                port.name.id
911            ),
912            ir::PortParent::FSM(fsm_wref) => format!(
913                "{}[{}]",
914                fsm_wref
915                    .internal
916                    .upgrade()
917                    .unwrap_or_else(|| panic!(
918                        "Malformed AST: No reference to FSM for port `{port:#?}'"
919                    ))
920                    .borrow()
921                    .name()
922                    .id,
923                port.name.id
924            ),
925            ir::PortParent::StaticGroup(group_wref) => format!(
926                "{}[{}]",
927                group_wref
928                    .internal
929                    .upgrade()
930                    .unwrap_or_else(|| panic!(
931                        "Malformed AST: No reference to Group for port `{port:#?}'"
932                    ))
933                    .borrow()
934                    .name()
935                    .id,
936                port.name.id
937            ),
938        }
939    }
940
941    /// Formats the top-level metadata if present
942    pub fn format_metadata(metadata: &Option<String>) -> String {
943        if let Some(metadata_str) = metadata {
944            format!("metadata #{{\n{metadata_str}\n}}#\n")
945        } else {
946            String::new()
947        }
948    }
949
950    pub fn write_source_info_table<W: io::Write>(
951        f: &mut W,
952        metadata: &Option<SourceInfoTable>,
953    ) -> Result<(), std::io::Error> {
954        if let Some(metadata) = metadata {
955            metadata.serialize(f)
956        } else {
957            Ok(())
958        }
959    }
960}