Skip to main content

rucc_sema/
print.rs

1//! The printer for the typed tree, which is what `--emit=tast` writes.
2//!
3//! Design: `spec/07-types-and-semantics.md` section 7.1.
4//!
5//! This one does not print C and does not try to. The tree it prints is not source any more:
6//! every conversion the language performs is a node of its own, so the shortest useful
7//! expression has more nodes than the program has operators, and writing that back as C would
8//! print exactly the text that hides what there is to see. What comes out instead is one node
9//! per line, indented by depth, with the type spelled out at every expression.
10//!
11//! The single most useful thing it does is make a conversion visible. When an IR bug turns out
12//! to be a sema bug, the question is almost always which conversion is missing or which one is
13//! the wrong one, and this is the artifact that answers it without a debugger.
14//!
15//! # Cross references
16//!
17//! A tree with jump tables in it is not a tree. A `switch` holds a table of cases whose bodies
18//! are statements inside its own body, and a `goto` names a label defined somewhere else
19//! entirely. Printing those by recursion would print the same statement twice, so they are
20//! printed as references instead, written `#n` after the word that says what kind of thing `n`
21//! counts: `case #3` is the fourth entry of the case table, `decl #3` the fourth declaration,
22//! `label #3` the fourth label. The numbers are arena indices, which is what makes a dump
23//! greppable: the definition and every use of one thing carry the same number.
24//!
25//! # Using it
26//!
27//! ```
28//! use rucc_base::Interner;
29//! use rucc_diag::Span;
30//! use rucc_sema::{Category, Const, Conversion, Expr, ExprKind, Printer, Tast};
31//! use rucc_types::{IntKind, Types};
32//!
33//! let types = Types::new();
34//! let names = Interner::new();
35//! let (char_type, int) = (types.int(IntKind::Char), types.int(IntKind::Int));
36//! let mut tast = Tast::new();
37//!
38//! let c = tast.add_const(Const::Int(97));
39//! let c = tast.expr(Expr::new(ExprKind::Const(c), char_type, Category::Rvalue), Span::DUMMY);
40//! let widened = ExprKind::Convert { kind: Conversion::Arithmetic, operand: c };
41//! let widened = tast.expr(Expr::new(widened, int, Category::Rvalue), Span::DUMMY);
42//!
43//! let mut printer = Printer::new(&tast, &types, &names);
44//! printer.expr(widened);
45//! assert_eq!(printer.finish(), "convert arithmetic : int\n  const 97 : char\n");
46//! ```
47
48use rucc_ast::AsmQuals;
49use rucc_base::Interner;
50use rucc_types::{TypeKind, Types, spell};
51
52use crate::asm::{AsmId, AsmOperandList};
53use crate::decl::{DeclId, DeclKind, Definition, Linkage, StorageDuration, Visibility};
54use crate::expr::{Category, Expr, ExprId, ExprKind};
55use crate::stmt::{CaseId, Stmt, StmtId};
56use crate::tast::{Base, Const, LabelId, Tast};
57
58/// The whole typed translation unit, as text.
59#[must_use]
60pub fn print(tast: &Tast, types: &Types, names: &Interner) -> String {
61    let mut printer = Printer::new(tast, types, names);
62    printer.unit();
63    printer.finish()
64}
65
66/// A typed tree being written out.
67///
68/// The whole unit is [`print()`]. This is here for the caller that wants one subtree, which is
69/// what a test wants and what a diagnostic that quotes a node would want.
70#[derive(Debug)]
71pub struct Printer<'a> {
72    tast: &'a Tast,
73    types: &'a Types,
74    names: &'a Interner,
75    out: String,
76    depth: usize,
77}
78
79impl<'a> Printer<'a> {
80    /// A printer over one tree, whose types are in `types` and whose names are in `names`.
81    #[must_use]
82    pub fn new(tast: &'a Tast, types: &'a Types, names: &'a Interner) -> Printer<'a> {
83        Printer { tast, types, names, out: String::new(), depth: 0 }
84    }
85
86    /// The text written so far.
87    #[must_use]
88    pub fn finish(self) -> String {
89        self.out
90    }
91
92    /// Every declaration of the translation unit, in the order they were declared.
93    pub fn unit(&mut self) {
94        for &id in self.tast.top_level() {
95            self.decl(id);
96        }
97    }
98
99    /// One declaration, and its initializer or its body.
100    pub fn decl(&mut self, id: DeclId) {
101        let node = &self.tast[id];
102        let mut head = format!("decl #{}", id.index());
103        if let Some(name) = node.name {
104            head.push(' ');
105            head.push_str(self.names.resolve(name));
106        }
107        head.push_str(" : ");
108        head.push_str(&spell(self.types, self.names, node.ty));
109        head.push_str(match node.kind {
110            DeclKind::Object => " object",
111            DeclKind::Function => " function",
112        });
113        head.push_str(match node.linkage {
114            Linkage::None => "",
115            Linkage::Internal => " internal",
116            Linkage::External => " external",
117        });
118        if node.kind == DeclKind::Object {
119            head.push_str(match node.duration {
120                StorageDuration::Static => " static",
121                StorageDuration::Thread => " thread",
122                StorageDuration::Automatic => " automatic",
123            });
124        }
125        head.push_str(match node.state {
126            Definition::Declared => " declared",
127            Definition::Tentative => " tentative",
128            Definition::Defined => " defined",
129        });
130        if node.constant {
131            head.push_str(" constexpr");
132        }
133        if let Some(align) = node.alignment {
134            head.push_str(&format!(" alignas {align}"));
135        }
136        if let Some(label) = node.asm_label {
137            head.push_str(&format!(" asm {}", self.tast[label].spell()));
138        }
139        if let Some(target) = node.alias {
140            head.push_str(&format!(" alias {}", self.tast[target].spell()));
141        }
142        // Only the reading that changes what is emitted is written, since the other two both mean
143        // that the definition is emitted and telling them apart is the merge's business.
144        if !node.inline.emits() {
145            head.push_str(" inline-definition");
146        }
147        if node.noreturn {
148            head.push_str(" noreturn");
149        }
150        // Only where a declaration said something, because the other way a name gets one is the
151        // command line and this dump is of a tree rather than of a compilation.
152        match node.visibility {
153            None => {}
154            Some(Visibility::Default) => head.push_str(" default-visibility"),
155            Some(Visibility::Hidden) => head.push_str(" hidden"),
156            Some(Visibility::Protected) => head.push_str(" protected"),
157        }
158        self.line(&head);
159
160        // An initializer that is present and empty is `= {}`, which zero-initializes and is not
161        // the same as no initializer at all, so the word is written whether there is anything
162        // under it or not.
163        if let Some(list) = node.init {
164            self.depth += 1;
165            self.line("init");
166            self.depth += 1;
167            // Copied out because printing a value takes `&mut self`, so the borrow of the
168            // table cannot be held across the walk. The same is true of every run below.
169            let entries = self.tast[list].to_vec();
170            for entry in entries {
171                let mut at = format!("+{}", entry.offset);
172                if entry.is_bit_field() {
173                    at.push_str(&format!(" bit {} width {}", entry.bit_offset, entry.bit_width));
174                }
175                self.line(&at);
176                self.depth += 1;
177                self.expr(entry.value);
178                self.depth -= 1;
179            }
180            self.depth -= 2;
181        }
182        // Before the body, because the body refers to them and a reader who meets `decl #1` in
183        // an expression should have been told what it is first.
184        let params = self.tast[id].params;
185        if !params.is_empty() {
186            self.depth += 1;
187            self.line("params");
188            self.depth += 1;
189            let params = self.tast[params].to_vec();
190            for param in params {
191                self.decl(param);
192            }
193            self.depth -= 2;
194        }
195        if let Some(body) = self.tast[id].body {
196            self.depth += 1;
197            self.line("body");
198            self.depth += 1;
199            self.stmt(body);
200            self.depth -= 2;
201        }
202    }
203
204    /// One statement and everything under it.
205    pub fn stmt(&mut self, id: StmtId) {
206        match self.tast[id] {
207            Stmt::Error => self.line("error"),
208            Stmt::Empty => self.line("empty"),
209            Stmt::Expr(value) => {
210                self.line("expr");
211                self.under(|p| p.expr(value));
212            }
213            Stmt::Block(body) => {
214                self.line("block");
215                self.depth += 1;
216                let body = self.tast[body].to_vec();
217                for stmt in body {
218                    self.stmt(stmt);
219                }
220                self.depth -= 1;
221            }
222            Stmt::Decls(decls) => {
223                self.line("decls");
224                self.depth += 1;
225                let decls = self.tast[decls].to_vec();
226                for decl in decls {
227                    self.decl(decl);
228                }
229                self.depth -= 1;
230            }
231            Stmt::If { cond, then, otherwise } => {
232                self.line("if");
233                self.depth += 1;
234                self.group("cond", |p| p.expr(cond));
235                self.group("then", |p| p.stmt(then));
236                if let Some(otherwise) = otherwise {
237                    self.group("else", |p| p.stmt(otherwise));
238                }
239                self.depth -= 1;
240            }
241            Stmt::While { cond, body } => {
242                self.line("while");
243                self.depth += 1;
244                self.group("cond", |p| p.expr(cond));
245                self.group("body", |p| p.stmt(body));
246                self.depth -= 1;
247            }
248            Stmt::DoWhile { body, cond } => {
249                self.line("do-while");
250                self.depth += 1;
251                self.group("body", |p| p.stmt(body));
252                self.group("cond", |p| p.expr(cond));
253                self.depth -= 1;
254            }
255            Stmt::For { init, cond, step, body } => {
256                self.line("for");
257                self.depth += 1;
258                if let Some(init) = init {
259                    self.group("init", |p| p.stmt(init));
260                }
261                if let Some(cond) = cond {
262                    self.group("cond", |p| p.expr(cond));
263                }
264                if let Some(step) = step {
265                    self.group("step", |p| p.expr(step));
266                }
267                self.group("body", |p| p.stmt(body));
268                self.depth -= 1;
269            }
270            Stmt::Switch { cond, body, cases, default } => {
271                self.line("switch");
272                self.depth += 1;
273                self.group("cond", |p| p.expr(cond));
274                self.line("cases");
275                self.depth += 1;
276                for index in cases.iter() {
277                    self.case(index);
278                }
279                if default.is_some() {
280                    self.line("default");
281                }
282                self.depth -= 1;
283                self.group("body", |p| p.stmt(body));
284                self.depth -= 1;
285            }
286            // The value is in the table under the `switch` and is not repeated here, so that
287            // the jump table has one home and a case in the body is a reference into it.
288            Stmt::Case { case, body } => {
289                self.line(&format!("case #{}", case.index()));
290                self.under(|p| p.stmt(body));
291            }
292            Stmt::Default { body } => {
293                self.line("default");
294                self.under(|p| p.stmt(body));
295            }
296            Stmt::Label { label, body } => {
297                let head = self.label(label);
298                self.line(&format!("label {head}"));
299                self.under(|p| p.stmt(body));
300            }
301            Stmt::Goto(label) => {
302                let target = self.label(label);
303                self.line(&format!("goto {target}"));
304            }
305            Stmt::IndirectGoto(target) => {
306                self.line("indirect-goto");
307                self.under(|p| p.expr(target));
308            }
309            Stmt::Asm(asm) => self.asm(asm),
310            Stmt::Break => self.line("break"),
311            Stmt::Continue => self.line("continue"),
312            Stmt::Return(None) => self.line("return"),
313            Stmt::Return(Some(value)) => {
314                self.line("return");
315                self.under(|p| p.expr(value));
316            }
317        }
318    }
319
320    /// One assembly statement, with its operands in the order the template numbers them.
321    ///
322    /// The operands are flat rather than grouped under `outputs` and `inputs`, because the
323    /// numbering runs through both of them and a reader counting to find `%2` should be able to
324    /// count lines. Each one says whether it travels as an address, which is a decision made
325    /// here rather than in the walk and is the kind of thing this dump exists to show.
326    fn asm(&mut self, id: AsmId) {
327        let node = self.tast[id];
328        let mut head = String::from("asm");
329        for (qual, name) in [
330            (AsmQuals::VOLATILE, " volatile"),
331            (AsmQuals::INLINE, " inline"),
332            (AsmQuals::GOTO, " goto"),
333        ] {
334            if node.quals.has(qual) {
335                head.push_str(name);
336            }
337        }
338        self.line(&head);
339        self.depth += 1;
340        self.line(&format!("template {}", self.tast[node.template].spell()));
341        self.asm_operands(node.outputs, "output");
342        self.asm_operands(node.inputs, "input");
343        for index in 0..self.tast[node.clobbers].len() {
344            let clobber = self.tast[node.clobbers][index];
345            self.line(&format!("clobber {}", self.tast[clobber].spell()));
346        }
347        for index in 0..self.tast[node.labels].len() {
348            let label = self.tast[node.labels][index];
349            let head = self.label(label);
350            self.line(&format!("label {head}"));
351        }
352        self.depth -= 1;
353    }
354
355    /// One section of an assembly statement's operands.
356    fn asm_operands(&mut self, list: AsmOperandList, what: &str) {
357        for index in 0..self.tast[list].len() {
358            let operand = self.tast[list][index];
359            let name = match operand.name {
360                Some(name) => format!(" [{}]", self.names.resolve(name)),
361                None => String::new(),
362            };
363            let memory = if operand.memory { " memory" } else { "" };
364            let constraint = self.tast[operand.constraint].spell();
365            self.line(&format!("{what}{name} {constraint}{memory}"));
366            self.under(|p| p.expr(operand.value));
367        }
368    }
369
370    /// One expression, its type, and everything under it.
371    pub fn expr(&mut self, id: ExprId) {
372        let node = self.tast[id];
373        let head = self.head(node);
374        let ty = spell(self.types, self.names, node.ty);
375        let category = match node.category {
376            Category::Rvalue => "",
377            Category::Lvalue => " lvalue",
378            Category::Bitfield => " bit-field",
379            Category::Function => " function",
380        };
381        self.line(&format!("{head} : {ty}{category}"));
382        self.depth += 1;
383        self.operands(node.kind);
384        self.depth -= 1;
385    }
386
387    /// What an expression is, without its type or its operands.
388    fn head(&self, node: Expr) -> String {
389        match node.kind {
390            ExprKind::Error => "error".to_owned(),
391            ExprKind::Const(value) => match self.tast[value] {
392                // Hexadecimal for the same reason the C printer uses it: a decimal spelling
393                // that reads back unchanged needs a shortest round trip algorithm, and one
394                // without such an algorithm quietly prints a different number.
395                Const::Int(value) => format!("const {value}"),
396                Const::Float(value) => format!("const {}", value.to_hex()),
397                Const::Address(address) => {
398                    let base = match address.base {
399                        Base::Decl(decl) => format!("decl #{}", decl.index()),
400                        Base::Str(id) => format!("string {}", self.tast[id].spell()),
401                    };
402                    format!("const address {base} + {}", address.offset)
403                }
404            },
405            ExprKind::Str(value) => format!("string {}", self.tast[value].spell()),
406            ExprKind::Decl(decl) => {
407                let mut head = format!("decl #{}", decl.index());
408                if let Some(name) = self.tast[decl].name {
409                    head.push(' ');
410                    head.push_str(self.names.resolve(name));
411                }
412                head
413            }
414            ExprKind::Member { base, field } => {
415                let mut head = format!("member #{field}");
416                if let Some(name) = self.field_name(base, field) {
417                    head.push(' ');
418                    head.push_str(name);
419                }
420                head
421            }
422            ExprKind::Subscript { .. } => "subscript".to_owned(),
423            ExprKind::Call { .. } => "call".to_owned(),
424            ExprKind::Unary { op, .. } if op.is_postfix() => {
425                format!("unary post {}", op.spelling())
426            }
427            ExprKind::Unary { op, .. } => format!("unary {}", op.spelling()),
428            ExprKind::Binary { op, .. } => format!("binary {}", op.spelling()),
429            // The computation type is written only when it is not the type of the assignment
430            // itself, which is the case that is worth seeing: `i /= 0.5` divides in `double`.
431            ExprKind::Assign { op, computation, .. } => {
432                let mut head = match op {
433                    None => "assign =".to_owned(),
434                    Some(op) => format!("assign {}=", op.spelling()),
435                };
436                if computation != node.ty {
437                    let ty = spell(self.types, self.names, computation);
438                    head.push_str(&format!(" in {ty}"));
439                }
440                head
441            }
442            ExprKind::Cond { .. } => "cond".to_owned(),
443            ExprKind::Comma { .. } => "comma".to_owned(),
444            ExprKind::Cast(_) => "cast".to_owned(),
445            ExprKind::Convert { kind, .. } => format!("convert {}", kind.as_str()),
446            ExprKind::CompoundLiteral(decl) => format!("compound-literal #{}", decl.index()),
447            ExprKind::StmtExpr(_) => "stmt-expr".to_owned(),
448            ExprKind::LabelAddr(label) => format!("label-addr {}", self.label(label)),
449            ExprKind::VaArg { .. } => "va-arg".to_owned(),
450            ExprKind::VaStart { .. } => "va-start".to_owned(),
451            ExprKind::VaEnd { .. } => "va-end".to_owned(),
452            ExprKind::VaCopy { .. } => "va-copy".to_owned(),
453            ExprKind::Classify { op, .. } => format!("classify {}", op.as_str()),
454            ExprKind::FpClassify { .. } => "fpclassify".to_owned(),
455            ExprKind::Sign { op, .. } => format!("sign {}", op.as_str()),
456            ExprKind::Abs { .. } => "abs".to_owned(),
457            ExprKind::ByteSwap { .. } => "bswap".to_owned(),
458            ExprKind::BitCount { count, .. } => format!("count {}", count.as_str()),
459            ExprKind::Overflow { op, at, .. } => {
460                format!("overflow {} at {}", op.as_str(), spell(self.types, self.names, at))
461            }
462            ExprKind::Atomic { op, order, .. } => {
463                format!("atomic {} {}", op.as_str(), order.as_str())
464            }
465            ExprKind::Unreachable => "unreachable".to_owned(),
466        }
467    }
468
469    /// Whatever hangs under an expression, already indented by the caller.
470    fn operands(&mut self, kind: ExprKind) {
471        match kind {
472            ExprKind::Error
473            | ExprKind::Const(_)
474            | ExprKind::Str(_)
475            | ExprKind::Decl(_)
476            | ExprKind::LabelAddr(_)
477            | ExprKind::Unreachable => {}
478            // A compound literal is a declaration of its own, printed where it is used, since
479            // it has no other place in the tree to be printed from.
480            ExprKind::CompoundLiteral(decl) => self.decl(decl),
481            ExprKind::StmtExpr(body) => self.stmt(body),
482            ExprKind::Member { base, .. }
483            | ExprKind::Cast(base)
484            | ExprKind::VaArg { list: base }
485            | ExprKind::VaStart { list: base }
486            | ExprKind::VaEnd { list: base }
487            | ExprKind::Convert { operand: base, .. }
488            | ExprKind::Abs { operand: base }
489            | ExprKind::ByteSwap { operand: base }
490            | ExprKind::BitCount { operand: base, .. }
491            | ExprKind::Unary { operand: base, .. } => self.expr(base),
492            ExprKind::Overflow { args, .. } | ExprKind::Atomic { args, .. } => {
493                let args = self.tast[args].to_vec();
494                for arg in args {
495                    self.expr(arg);
496                }
497            }
498            ExprKind::Subscript { base: lhs, index: rhs }
499            | ExprKind::Binary { lhs, rhs, .. }
500            | ExprKind::Assign { lhs, rhs, .. }
501            | ExprKind::VaCopy { dst: lhs, src: rhs }
502            | ExprKind::Comma { lhs, rhs } => {
503                self.expr(lhs);
504                self.expr(rhs);
505            }
506            ExprKind::Call { callee, args } => {
507                self.expr(callee);
508                let args = self.tast[args].to_vec();
509                for arg in args {
510                    self.expr(arg);
511                }
512            }
513            ExprKind::Cond { cond, then, otherwise } => {
514                self.expr(cond);
515                self.expr(then);
516                self.expr(otherwise);
517            }
518            ExprKind::Classify { lhs, rhs, .. } | ExprKind::Sign { lhs, rhs, .. } => {
519                self.expr(lhs);
520                if let Some(rhs) = rhs {
521                    self.expr(rhs);
522                }
523            }
524            // The value first, the way the node holds it, and the five answers after it in the
525            // order the call writes them rather than the order the call is written in.
526            ExprKind::FpClassify { value, answers } => {
527                self.expr(value);
528                let answers = self.tast[answers].to_vec();
529                for answer in answers {
530                    self.expr(answer);
531                }
532            }
533        }
534    }
535
536    /// One entry of a case table, which is a value or a range of them.
537    fn case(&mut self, id: CaseId) {
538        let case = self.tast[id];
539        let head = if case.low == case.high {
540            format!("case #{} {}", id.index(), case.low)
541        } else {
542            format!("case #{} {} ... {}", id.index(), case.low, case.high)
543        };
544        self.line(&head);
545    }
546
547    /// A label, as its index and its name.
548    fn label(&self, id: LabelId) -> String {
549        format!("#{} {}", id.index(), self.names.resolve(self.tast[id].name))
550    }
551
552    /// The name of the member at an index, where the base is a record that has one there.
553    ///
554    /// It is a convenience and not a fact the tree depends on. The index is what the node
555    /// holds, an anonymous member has no name to print, and a member of an incomplete record
556    /// cannot happen but is not worth panicking over in a printer.
557    fn field_name(&self, base: ExprId, field: u32) -> Option<&'a str> {
558        let ty = self.types.canonical(self.tast[base].ty);
559        let TypeKind::Record(record) = self.types.kind(ty) else { return None };
560        let field = self.types.record_info(record).fields.get(field as usize)?;
561        Some(self.names.resolve(field.name?))
562    }
563
564    /// Writes a named group and puts what the closure writes one level under it.
565    fn group(&mut self, name: &str, write: impl FnOnce(&mut Printer<'a>)) {
566        self.line(name);
567        self.under(write);
568    }
569
570    /// Writes what the closure writes one level in.
571    fn under(&mut self, write: impl FnOnce(&mut Printer<'a>)) {
572        self.depth += 1;
573        write(self);
574        self.depth -= 1;
575    }
576
577    /// Writes one line at the current depth.
578    fn line(&mut self, text: &str) {
579        for _ in 0..self.depth {
580            self.out.push_str("  ");
581        }
582        self.out.push_str(text);
583        self.out.push('\n');
584    }
585}
586
587#[cfg(test)]
588mod tests {
589    use rucc_ast::{BinaryOp, UnaryOp};
590    use rucc_diag::Span;
591    use rucc_types::{ArrayLen, IntKind};
592
593    use super::*;
594    use crate::decl::{Decl, DeclList, Emission, InitEntry};
595    use crate::expr::{Conversion, Expr};
596    use crate::stmt::Case;
597    use crate::tast::Label;
598
599    struct Fixture {
600        tast: Tast,
601        types: Types,
602        names: Interner,
603    }
604
605    impl Fixture {
606        fn new() -> Fixture {
607            Fixture { tast: Tast::new(), types: Types::new(), names: Interner::new() }
608        }
609
610        fn int(&self) -> rucc_types::TypeId {
611            self.types.int(IntKind::Int)
612        }
613
614        /// An rvalue of the given type and kind, which is most of what a test needs.
615        fn value(&mut self, kind: ExprKind, ty: rucc_types::TypeId) -> ExprId {
616            self.tast.expr(Expr::new(kind, ty, Category::Rvalue), Span::DUMMY)
617        }
618
619        fn constant(&mut self, value: i128, ty: rucc_types::TypeId) -> ExprId {
620            let id = self.tast.add_const(Const::Int(value));
621            self.value(ExprKind::Const(id), ty)
622        }
623
624        fn text(&self, write: impl FnOnce(&mut Printer<'_>)) -> String {
625            let mut printer = Printer::new(&self.tast, &self.types, &self.names);
626            write(&mut printer);
627            printer.finish()
628        }
629    }
630
631    #[test]
632    fn an_expression_carries_its_type_on_every_line() {
633        let mut f = Fixture::new();
634        let int = f.int();
635        let left = f.constant(1, int);
636        let right = f.constant(2, int);
637        let sum = f.value(ExprKind::Binary { op: BinaryOp::Add, lhs: left, rhs: right }, int);
638
639        assert_eq!(f.text(|p| p.expr(sum)), "binary + : int\n  const 1 : int\n  const 2 : int\n");
640    }
641
642    #[test]
643    fn a_conversion_is_what_the_dump_is_for() {
644        let mut f = Fixture::new();
645        let (char_type, long) = (f.types.int(IntKind::Char), f.types.int(IntKind::Long));
646        let object = f.tast.decl(object_decl(char_type), Span::DUMMY);
647        let name = f
648            .tast
649            .expr(Expr::new(ExprKind::Decl(object), char_type, Category::Lvalue), Span::DUMMY);
650        let read =
651            f.value(ExprKind::Convert { kind: Conversion::Lvalue, operand: name }, char_type);
652        let widened =
653            f.value(ExprKind::Convert { kind: Conversion::Arithmetic, operand: read }, long);
654
655        // The two steps that got a `char` to a `long` are each a line, which is the whole
656        // reason this printer exists rather than one that writes the C back.
657        assert_eq!(
658            f.text(|p| p.expr(widened)),
659            "convert arithmetic : long\n  convert lvalue : char\n    decl #0 : char lvalue\n"
660        );
661    }
662
663    #[test]
664    fn a_category_is_written_and_an_rvalue_is_the_silent_one() {
665        let mut f = Fixture::new();
666        let int = f.int();
667        let object = f.tast.decl(object_decl(int), Span::DUMMY);
668        let name =
669            f.tast.expr(Expr::new(ExprKind::Decl(object), int, Category::Lvalue), Span::DUMMY);
670        let bits =
671            f.tast.expr(Expr::new(ExprKind::Decl(object), int, Category::Bitfield), Span::DUMMY);
672
673        assert_eq!(f.text(|p| p.expr(name)), "decl #0 : int lvalue\n");
674        assert_eq!(f.text(|p| p.expr(bits)), "decl #0 : int bit-field\n");
675    }
676
677    #[test]
678    fn a_postfix_operator_is_not_printed_as_the_prefix_one() {
679        let mut f = Fixture::new();
680        let int = f.int();
681        let one = f.constant(1, int);
682        let post = f.value(ExprKind::Unary { op: UnaryOp::PostInc, operand: one }, int);
683        let pre = f.value(ExprKind::Unary { op: UnaryOp::PreInc, operand: one }, int);
684
685        assert!(f.text(|p| p.expr(post)).starts_with("unary post ++"));
686        assert!(f.text(|p| p.expr(pre)).starts_with("unary ++ :"));
687    }
688
689    #[test]
690    fn a_compound_assignment_keeps_its_operator() {
691        let mut f = Fixture::new();
692        let int = f.int();
693        let one = f.constant(1, int);
694        let plain =
695            f.value(ExprKind::Assign { op: None, computation: int, lhs: one, rhs: one }, int);
696        let shl =
697            ExprKind::Assign { op: Some(BinaryOp::Shl), computation: int, lhs: one, rhs: one };
698        let compound = f.value(shl, int);
699
700        assert!(f.text(|p| p.expr(plain)).starts_with("assign = :"));
701        assert!(f.text(|p| p.expr(compound)).starts_with("assign <<= :"));
702    }
703
704    #[test]
705    fn a_case_is_a_reference_into_the_table_and_not_a_second_copy_of_it() {
706        let mut f = Fixture::new();
707        let int = f.int();
708        let cond = f.constant(0, int);
709        let empty = f.tast.stmt(Stmt::Empty, Span::DUMMY);
710        let cases = f.tast.add_cases(&[
711            Case { low: 1, high: 1, body: empty },
712            Case { low: 2, high: 9, body: empty },
713        ]);
714        let first = f.tast.stmt(
715            Stmt::Case { case: cases.iter().next().expect("a case"), body: empty },
716            Span::DUMMY,
717        );
718        let fallback = f.tast.stmt(Stmt::Default { body: empty }, Span::DUMMY);
719        let body = f.tast.add_stmt_refs(&[first, fallback]);
720        let body = f.tast.stmt(Stmt::Block(body), Span::DUMMY);
721        let switch =
722            f.tast.stmt(Stmt::Switch { cond, body, cases, default: Some(empty) }, Span::DUMMY);
723
724        assert_eq!(
725            f.text(|p| p.stmt(switch)),
726            "\
727switch
728  cond
729    const 0 : int
730  cases
731    case #0 1
732    case #1 2 ... 9
733    default
734  body
735    block
736      case #0
737        empty
738      default
739        empty
740"
741        );
742    }
743
744    #[test]
745    fn a_label_and_the_goto_that_reaches_it_carry_the_same_number() {
746        let mut f = Fixture::new();
747        let name = f.names.intern("done");
748        let label = f.tast.add_label(Label { name, stmt: None });
749        let empty = f.tast.stmt(Stmt::Empty, Span::DUMMY);
750        let target = f.tast.stmt(Stmt::Label { label, body: empty }, Span::DUMMY);
751        let jump = f.tast.stmt(Stmt::Goto(label), Span::DUMMY);
752        f.tast.define_label(label, target);
753
754        assert_eq!(f.text(|p| p.stmt(target)), "label #0 done\n  empty\n");
755        assert_eq!(f.text(|p| p.stmt(jump)), "goto #0 done\n");
756    }
757
758    #[test]
759    fn a_declaration_says_what_it_is_and_an_empty_initializer_is_still_one() {
760        let mut f = Fixture::new();
761        let int = f.int();
762        let array = f.types.array(int, ArrayLen::Fixed(2));
763        let mut decl = object_decl(array);
764        decl.name = Some(f.names.intern("a"));
765        decl.linkage = Linkage::Internal;
766        decl.duration = StorageDuration::Static;
767        decl.alignment = Some(16);
768        decl.init = Some(f.tast.add_init_entries(&[]));
769        let id = f.tast.decl(decl, Span::DUMMY);
770
771        assert_eq!(
772            f.text(|p| p.decl(id)),
773            "decl #0 a : int[2] object internal static defined alignas 16\n  init\n"
774        );
775    }
776
777    #[test]
778    fn an_initializer_prints_where_each_value_goes() {
779        let mut f = Fixture::new();
780        let int = f.int();
781        let array = f.types.array(int, ArrayLen::Fixed(2));
782        let one = f.constant(1, int);
783        let entries = f.tast.add_init_entries(&[
784            InitEntry::at(0, one),
785            InitEntry { offset: 4, value: one, bit_offset: 3, bit_width: 5 },
786        ]);
787        let mut decl = object_decl(array);
788        decl.init = Some(entries);
789        let id = f.tast.decl(decl, Span::DUMMY);
790
791        assert_eq!(
792            f.text(|p| p.decl(id)),
793            "\
794decl #0 : int[2] object automatic defined
795  init
796    +0
797      const 1 : int
798    +4 bit 3 width 5
799      const 1 : int
800"
801        );
802    }
803
804    #[test]
805    fn a_unit_is_its_declarations_in_order() {
806        let mut f = Fixture::new();
807        let int = f.int();
808        let first = f.tast.decl(object_decl(int), Span::DUMMY);
809        let second = f.tast.decl(object_decl(int), Span::DUMMY);
810        f.tast.add_top_level(first);
811        f.tast.add_top_level(second);
812
813        assert_eq!(
814            print(&f.tast, &f.types, &f.names),
815            "decl #0 : int object automatic defined\ndecl #1 : int object automatic defined\n"
816        );
817    }
818
819    fn object_decl(ty: rucc_types::TypeId) -> Decl {
820        Decl {
821            name: None,
822            ty,
823            kind: DeclKind::Object,
824            linkage: Linkage::None,
825            duration: StorageDuration::Automatic,
826            state: Definition::Defined,
827            alignment: None,
828            constant: false,
829            retained: false,
830            asm_label: None,
831            alias: None,
832            inline: Emission::Silent,
833            gnu_inline: false,
834            noreturn: false,
835            visibility: None,
836            init: None,
837            params: DeclList::EMPTY,
838            body: None,
839        }
840    }
841}