Skip to main content

lift_core/
printer.rs

1use crate::attributes::Attribute;
2use crate::blocks::BlockKey;
3use crate::context::Context;
4use crate::operations::OpKey;
5use crate::regions::RegionKey;
6use crate::values::ValueKey;
7use std::fmt::Write;
8
9pub struct Printer<'a> {
10    ctx: &'a Context,
11    indent: usize,
12    output: String,
13    value_names: std::collections::HashMap<ValueKey, String>,
14    next_value_id: u32,
15    next_block_id: u32,
16    block_names: std::collections::HashMap<BlockKey, String>,
17}
18
19impl<'a> Printer<'a> {
20    pub fn new(ctx: &'a Context) -> Self {
21        Self {
22            ctx,
23            indent: 0,
24            output: String::new(),
25            value_names: std::collections::HashMap::new(),
26            next_value_id: 0,
27            next_block_id: 0,
28            block_names: std::collections::HashMap::new(),
29        }
30    }
31
32    pub fn print_all(&mut self) -> &str {
33        for module in &self.ctx.modules {
34            let name = self.ctx.strings.resolve(module.name);
35            self.write_line(&format!("module @{} {{", name));
36            self.indent += 1;
37
38            for func in &module.functions {
39                self.print_function(func);
40            }
41
42            self.indent -= 1;
43            self.write_line("}");
44            self.write_line("");
45        }
46
47        // Print any standalone blocks/ops not in modules
48        for (block_key, _block) in &self.ctx.blocks {
49            if self.ctx.blocks[block_key].parent_region.is_none() {
50                self.print_block(block_key, true);
51            }
52        }
53
54        &self.output
55    }
56
57    fn print_function(&mut self, func: &crate::functions::FunctionData) {
58        let name = self.ctx.strings.resolve(func.name);
59        let mut sig = format!("func @{}(", name);
60
61        // The entry block's arguments are the actual values the body refers
62        // to (see lift-ast's builder, which creates them directly from the
63        // parameter list). Print those real names instead of disconnected
64        // fresh ones, so the signature and body agree and the printed .lif
65        // can be parsed back in.
66        let mut entry_args: Option<Vec<ValueKey>> = None;
67        if let Some(region) = func.body {
68            if let Some(r) = self.ctx.get_region(region) {
69                if let Some(&block_key) = r.blocks.first() {
70                    if let Some(block) = self.ctx.get_block(block_key) {
71                        entry_args = Some(block.args.clone());
72                    }
73                }
74            }
75        }
76
77        if let Some(args) = entry_args {
78            for (i, arg) in args.into_iter().enumerate() {
79                if i > 0 {
80                    sig.push_str(", ");
81                }
82                let vname = self.get_value_name(arg);
83                if let Some(val) = self.ctx.get_value(arg) {
84                    let _ = write!(sig, "%{}: {}", vname, self.format_type(val.ty));
85                }
86            }
87        } else {
88            for (i, &param_ty) in func.params.iter().enumerate() {
89                if i > 0 {
90                    sig.push_str(", ");
91                }
92                let pname = self.fresh_value_name();
93                let _ = write!(sig, "%{}: {}", pname, self.format_type(param_ty));
94            }
95        }
96
97        sig.push_str(") -> ");
98        if func.returns.len() == 1 {
99            let _ = write!(sig, "{}", self.format_type(func.returns[0]));
100        } else {
101            sig.push('(');
102            for (i, &ret_ty) in func.returns.iter().enumerate() {
103                if i > 0 {
104                    sig.push_str(", ");
105                }
106                let _ = write!(sig, "{}", self.format_type(ret_ty));
107            }
108            sig.push(')');
109        }
110
111        if let Some(body) = func.body {
112            sig.push_str(" {");
113            self.write_line(&sig);
114            self.indent += 1;
115            self.print_function_body(body);
116            self.indent -= 1;
117            self.write_line("}");
118        } else {
119            self.write_line(&sig);
120        }
121        self.write_line("");
122    }
123
124    fn print_region(&mut self, region_key: RegionKey) {
125        if let Some(region) = self.ctx.get_region(region_key) {
126            for &block_key in &region.blocks {
127                self.print_block(block_key, true);
128            }
129        }
130    }
131
132    /// Prints a function's body region. The entry block's args were already
133    /// declared in the function signature (`print_function`), so its
134    /// `^bb0(...):` header would be redundant — and unparseable, since the
135    /// current `.lif` grammar has no rule for block labels (every function
136    /// is single-block). Any further blocks (not producible by the parser
137    /// today, but structurally possible) still get a normal header.
138    fn print_function_body(&mut self, region_key: RegionKey) {
139        if let Some(region) = self.ctx.get_region(region_key) {
140            for (i, &block_key) in region.blocks.iter().enumerate() {
141                self.print_block(block_key, i != 0);
142            }
143        }
144    }
145
146    fn print_block(&mut self, block_key: BlockKey, print_header: bool) {
147        if print_header {
148            let block_name = self.get_block_name(block_key);
149            if let Some(block) = self.ctx.get_block(block_key) {
150                if !block.args.is_empty() {
151                    let mut args = String::new();
152                    for (i, &arg) in block.args.iter().enumerate() {
153                        if i > 0 {
154                            args.push_str(", ");
155                        }
156                        let vname = self.get_value_name(arg);
157                        if let Some(val) = self.ctx.get_value(arg) {
158                            let _ = write!(args, "%{}: {}", vname, self.format_type(val.ty));
159                        }
160                    }
161                    self.write_line(&format!("^{}({}):", block_name, args));
162                } else {
163                    self.write_line(&format!("^{}:", block_name));
164                }
165            }
166            self.indent += 1;
167        }
168
169        if let Some(block) = self.ctx.get_block(block_key) {
170            for &op_key in &block.ops {
171                self.print_op(op_key);
172            }
173        }
174
175        if print_header {
176            self.indent -= 1;
177        }
178    }
179
180    fn print_op(&mut self, op_key: OpKey) {
181        if let Some(op) = self.ctx.get_op(op_key) {
182            let op_name = self.ctx.strings.resolve(op.name).to_string();
183            let mut line = String::new();
184
185            // Result values
186            if !op.results.is_empty() {
187                for (i, &result) in op.results.iter().enumerate() {
188                    if i > 0 {
189                        line.push_str(", ");
190                    }
191                    let vname = self.get_value_name(result);
192                    let _ = write!(line, "%{}", vname);
193                }
194                line.push_str(" = ");
195            }
196
197            // Operation name
198            let _ = write!(line, "\"{}\"", op_name);
199
200            // Inputs
201            line.push('(');
202            for (i, &input) in op.inputs.iter().enumerate() {
203                if i > 0 {
204                    line.push_str(", ");
205                }
206                let vname = self.get_value_name(input);
207                let _ = write!(line, "%{}", vname);
208            }
209            line.push(')');
210
211            // Attributes
212            if !op.attrs.is_empty() {
213                line.push_str(" {");
214                for (i, (key, val)) in op.attrs.iter().enumerate() {
215                    if i > 0 {
216                        line.push_str(", ");
217                    }
218                    let _ = write!(line, "{} = {}", key, self.format_attr(val));
219                }
220                line.push('}');
221            }
222
223            // Type signature
224            line.push_str(" : (");
225            for (i, &input) in op.inputs.iter().enumerate() {
226                if i > 0 {
227                    line.push_str(", ");
228                }
229                if let Some(val) = self.ctx.get_value(input) {
230                    let _ = write!(line, "{}", self.format_type(val.ty));
231                }
232            }
233            line.push_str(") -> ");
234
235            if op.results.len() == 1 {
236                if let Some(val) = self.ctx.get_value(op.results[0]) {
237                    let _ = write!(line, "{}", self.format_type(val.ty));
238                }
239            } else if op.results.is_empty() {
240                line.push_str("()");
241            } else {
242                line.push('(');
243                for (i, &result) in op.results.iter().enumerate() {
244                    if i > 0 {
245                        line.push_str(", ");
246                    }
247                    if let Some(val) = self.ctx.get_value(result) {
248                        let _ = write!(line, "{}", self.format_type(val.ty));
249                    }
250                }
251                line.push(')');
252            }
253
254            self.write_line(&line);
255
256            // Print nested regions
257            if !op.regions.is_empty() {
258                for &region in &op.regions {
259                    self.indent += 1;
260                    self.print_region(region);
261                    self.indent -= 1;
262                }
263            }
264        }
265    }
266
267    fn format_type(&self, ty_id: crate::types::TypeId) -> String {
268        let ty = self.ctx.resolve_type(ty_id);
269        format!("{}", ty)
270    }
271
272    fn format_attr(&self, attr: &Attribute) -> String {
273        match attr {
274            Attribute::Integer(v) => format!("{}", v),
275            Attribute::Float(v) => format!("{:.6}", v),
276            Attribute::String(s) => {
277                let resolved = self.ctx.strings.resolve(*s);
278                format!("\"{}\"", resolved)
279            }
280            Attribute::Bool(b) => format!("{}", b),
281            Attribute::Type(_) => "type".to_string(),
282            Attribute::Array(arr) => {
283                let inner: Vec<String> = arr.iter().map(|a| self.format_attr(a)).collect();
284                format!("[{}]", inner.join(", "))
285            }
286            Attribute::Dict(map) => {
287                let inner: Vec<String> = map
288                    .iter()
289                    .map(|(k, v)| format!("{}: {}", k, self.format_attr(v)))
290                    .collect();
291                format!("{{{}}}", inner.join(", "))
292            }
293        }
294    }
295
296    fn get_value_name(&mut self, key: ValueKey) -> String {
297        if let Some(name) = self.value_names.get(&key) {
298            return name.clone();
299        }
300
301        // Try to use the debug name if available
302        let name = if let Some(val) = self.ctx.get_value(key) {
303            if let Some(name_id) = val.name {
304                self.ctx.strings.resolve(name_id).to_string()
305            } else {
306                self.fresh_value_name()
307            }
308        } else {
309            self.fresh_value_name()
310        };
311
312        self.value_names.insert(key, name.clone());
313        name
314    }
315
316    fn fresh_value_name(&mut self) -> String {
317        let name = format!("v{}", self.next_value_id);
318        self.next_value_id += 1;
319        name
320    }
321
322    fn get_block_name(&mut self, key: BlockKey) -> String {
323        if let Some(name) = self.block_names.get(&key) {
324            return name.clone();
325        }
326        let name = format!("bb{}", self.next_block_id);
327        self.next_block_id += 1;
328        self.block_names.insert(key, name.clone());
329        name
330    }
331
332    fn write_line(&mut self, text: &str) {
333        for _ in 0..self.indent {
334            self.output.push_str("    ");
335        }
336        self.output.push_str(text);
337        self.output.push('\n');
338    }
339
340    pub fn into_string(self) -> String {
341        self.output
342    }
343}
344
345pub fn print_ir(ctx: &Context) -> String {
346    let mut printer = Printer::new(ctx);
347    printer.print_all();
348    printer.into_string()
349}
350
351#[cfg(test)]
352mod tests {
353    use super::*;
354
355    #[test]
356    fn test_print_empty_context() {
357        let ctx = Context::new();
358        let output = print_ir(&ctx);
359        assert!(output.is_empty() || output.trim().is_empty());
360    }
361}