Skip to main content

i_slint_compiler/llr/
pretty_print.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4use std::fmt::{Display, Result, Write};
5
6use itertools::{Either, Itertools};
7
8use crate::expression_tree::MinMaxOp;
9use crate::langtype::{StructName, Type};
10use crate::layout::Orientation;
11
12use super::{
13    Animation, CompilationUnit, EvaluationContext, Expression, LocalMemberIndex,
14    LocalMemberReference, MemberReference, ParentScope, SubComponentIdx,
15};
16
17pub fn pretty_print(root: &CompilationUnit, writer: &mut dyn Write) -> Result {
18    PrettyPrinter { writer, indentation: 0 }.print_root(root)
19}
20
21/// Print compiler-internal builtin structs by their name; they have no slint
22/// name, so `Type`'s Display spells out all their fields.
23struct DisplayType<'a>(&'a Type);
24impl Display for DisplayType<'_> {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
26        match self.0 {
27            Type::Struct(s) => match &s.name {
28                StructName::Builtin(b) if b.slint_name().is_none() => {
29                    write!(f, "{}", <&str>::from(b))
30                }
31                _ => write!(f, "{}", self.0),
32            },
33            _ => write!(f, "{}", self.0),
34        }
35    }
36}
37
38struct PrettyPrinter<'a> {
39    writer: &'a mut dyn Write,
40    indentation: usize,
41}
42
43impl PrettyPrinter<'_> {
44    fn print_root(&mut self, root: &CompilationUnit) -> Result {
45        for (idx, g) in root.globals.iter_enumerated() {
46            if !g.is_builtin {
47                self.print_global(root, idx, g)?;
48            }
49        }
50        // Repeater, popup, and menu trees print inline under their parent,
51        // because their expressions resolve in the parent scope.
52        for c in &root.used_sub_components {
53            self.print_component(root, *c, None)?
54        }
55        for p in &root.public_components {
56            self.print_component(root, p.item_tree.root, None)?
57        }
58        if let Some(p) = &root.popup_menu {
59            self.print_component(root, p.item_tree.root, None)?
60        }
61
62        Ok(())
63    }
64
65    fn print_component(
66        &mut self,
67        root: &CompilationUnit,
68        sc_idx: SubComponentIdx,
69        parent: Option<&ParentScope<'_>>,
70    ) -> Result {
71        let ctx = EvaluationContext::new_sub_component(root, sc_idx, (), parent);
72        let sc = &root.sub_components[sc_idx];
73        writeln!(self.writer, "component {} {{", sc.name)?;
74        self.indentation += 1;
75        for p in &sc.properties {
76            self.indent()?;
77            writeln!(
78                self.writer,
79                "property <{}> {}; //use={}",
80                DisplayType(&p.ty),
81                p.name,
82                p.use_count.get()
83            )?;
84        }
85        for c in &sc.callbacks {
86            self.indent()?;
87            writeln!(
88                self.writer,
89                "callback {} ({}) -> {};",
90                c.name,
91                c.args.iter().map(|t| DisplayType(t).to_string()).join(", "),
92                DisplayType(&c.ret_ty),
93            )?;
94        }
95        for f in &sc.functions {
96            self.indent()?;
97            writeln!(
98                self.writer,
99                "function {} ({}) -> {} {{ {} }}; ",
100                f.name,
101                f.args.iter().map(|t| DisplayType(t).to_string()).join(", "),
102                DisplayType(&f.ret_ty),
103                DisplayExpression(&f.code.borrow(), &ctx)
104            )?;
105        }
106        for twb in &sc.two_way_bindings {
107            self.indent()?;
108            writeln!(
109                self.writer,
110                "{} <=> {}{}{};",
111                DisplayLocalRef(&twb.prop1, &ctx),
112                DisplayPropertyRef(&twb.prop2, &ctx),
113                if twb.field_access.is_empty() { "" } else { "." },
114                twb.field_access.join(".")
115            )?
116        }
117        for (p, init) in &sc.property_init {
118            self.indent()?;
119            write!(
120                self.writer,
121                "{}: {}",
122                DisplayPropertyRef(p, &ctx),
123                DisplayExpression(&init.expression.borrow(), &ctx)
124            )?;
125            match &init.animation {
126                Some(Animation::Static(a)) => {
127                    write!(self.writer, " animate {}", DisplayExpression(a, &ctx))?
128                }
129                Some(Animation::Transition(a)) => {
130                    write!(self.writer, " animate transition {}", DisplayExpression(a, &ctx))?
131                }
132                None => {}
133            }
134            writeln!(
135                self.writer,
136                ";{}",
137                if init.kind == super::BindingKind::Constant { " /*const*/" } else { "" }
138            )?
139        }
140        for (p, a) in &sc.animations {
141            self.indent()?;
142            writeln!(
143                self.writer,
144                "animate {} {{ {} }};",
145                DisplayLocalRef(p, &ctx),
146                DisplayExpression(a, &ctx)
147            )?
148        }
149        for (p, e) in &sc.change_callbacks {
150            self.indent()?;
151            writeln!(
152                self.writer,
153                "changed {} => {};",
154                DisplayPropertyRef(p, &ctx),
155                DisplayExpression(&e.borrow(), &ctx),
156            )?
157        }
158        for e in &sc.pre_init_code {
159            self.indent()?;
160            writeln!(self.writer, "pre-init => {};", DisplayExpression(&e.borrow(), &ctx))?
161        }
162        for e in &sc.init_code {
163            self.indent()?;
164            writeln!(self.writer, "init => {};", DisplayExpression(&e.borrow(), &ctx))?
165        }
166        for (name, e) in
167            [("layout-info-h", &sc.layout_info_h), ("layout-info-v", &sc.layout_info_v)]
168        {
169            self.indent()?;
170            writeln!(self.writer, "{}: {};", name, DisplayExpression(&e.borrow(), &ctx))?
171        }
172        if let Some(e) = &sc.grid_layout_input_for_repeated {
173            self.indent()?;
174            writeln!(
175                self.writer,
176                "grid-layout-input-for-repeated: {};",
177                DisplayExpression(&e.borrow(), &ctx)
178            )?
179        }
180        if let Some(e) = &sc.flexbox_layout_item_info_for_repeated {
181            self.indent()?;
182            writeln!(
183                self.writer,
184                "flexbox-layout-item-info-for-repeated: {};",
185                DisplayExpression(&e.borrow(), &ctx)
186            )?
187        }
188        if let Some((cross_o, e)) = &sc.cross_axis_self_alignment_for_repeated {
189            self.indent()?;
190            writeln!(
191                self.writer,
192                "cross-axis-self-alignment-for-repeated ({cross_o:?}): {};",
193                DisplayExpression(&e.borrow(), &ctx)
194            )?
195        }
196        if let Some((main_o, e)) = &sc.layout_order_for_repeated {
197            self.indent()?;
198            writeln!(
199                self.writer,
200                "layout-order-for-repeated ({main_o:?}): {};",
201                DisplayExpression(&e.borrow(), &ctx)
202            )?
203        }
204        for (i, c) in sc.grid_layout_children.iter_enumerated() {
205            self.indent()?;
206            writeln!(
207                self.writer,
208                "grid-layout-child[{}] {{ h: {}; v: {} }};",
209                usize::from(i),
210                DisplayExpression(&c.layout_info_h.borrow(), &ctx),
211                DisplayExpression(&c.layout_info_v.borrow(), &ctx)
212            )?
213        }
214        for t in &sc.timers {
215            self.indent()?;
216            writeln!(
217                self.writer,
218                "timer {{ interval: {}; running: {}; triggered => {} }};",
219                DisplayExpression(&t.interval.borrow(), &ctx),
220                DisplayExpression(&t.running.borrow(), &ctx),
221                DisplayExpression(&t.triggered.borrow(), &ctx)
222            )?
223        }
224        for ssc in &sc.sub_components {
225            self.indent()?;
226            writeln!(self.writer, "{} := {} {{}};", ssc.name, root.sub_components[ssc.ty].name)?;
227        }
228        for (item, geom) in std::iter::zip(&sc.items, &sc.geometries) {
229            self.indent()?;
230            let geometry = geom.as_ref().map_or(String::new(), |geom| {
231                format!("geometry: {}", DisplayExpression(&geom.borrow(), &ctx))
232            });
233            writeln!(self.writer, "{} := {} {{ {geometry} }};", item.name, item.ty.class_name)?;
234        }
235        for ((item_index, prop), e) in &sc.accessible_prop {
236            self.indent()?;
237            writeln!(
238                self.writer,
239                "{}.accessible-{}: {};",
240                item_name_in_tree(root, sc, *item_index)
241                    .unwrap_or_else(|| format!("@{item_index}")),
242                crate::generator::to_kebab_case(prop),
243                DisplayExpression(&e.borrow(), &ctx)
244            )?
245        }
246        for (idx, r) in sc.repeated.iter_enumerated() {
247            self.indent()?;
248            write!(
249                self.writer,
250                "{} {} : /*@repeater({})*/ ",
251                if r.index_prop.is_none() && r.data_prop.is_none() { "if" } else { "for in" },
252                DisplayExpression(&r.model.borrow(), &ctx),
253                usize::from(idx)
254            )?;
255            self.print_component(root, r.sub_tree.root, Some(&ParentScope::new(&ctx, Some(idx))))?
256        }
257        for (i, t) in sc.menu_item_trees.iter().enumerate() {
258            self.indent()?;
259            write!(self.writer, "menu : /*@menu({i})*/ ")?;
260            self.print_component(root, t.root, Some(&ParentScope::new(&ctx, None)))?
261        }
262        for (i, w) in sc.popup_windows.iter().enumerate() {
263            self.indent()?;
264            let parent = ParentScope::new(&ctx, None);
265            // The position is evaluated in the popup's own scope.
266            let popup_ctx =
267                EvaluationContext::new_sub_component(root, w.item_tree.root, (), Some(&parent));
268            write!(
269                self.writer,
270                "{} at {} : /*@popup({i})*/ ",
271                if w.is_tooltip { "tooltip" } else { "popup" },
272                DisplayExpression(&w.position.borrow(), &popup_ctx)
273            )?;
274            self.print_component(root, w.item_tree.root, Some(&parent))?
275        }
276        self.indentation -= 1;
277        self.indent()?;
278        writeln!(self.writer, "}}")
279    }
280
281    fn print_global(
282        &mut self,
283        root: &CompilationUnit,
284        idx: super::GlobalIdx,
285        global: &super::GlobalComponent,
286    ) -> Result {
287        let ctx = EvaluationContext::new_global(root, idx, ());
288        if global.exported {
289            write!(self.writer, "export ")?;
290        }
291        let aliases = global.aliases.join(",");
292        let aliases = if aliases.is_empty() { String::new() } else { format!(" /*{aliases}*/") };
293        let emission = if global.from_library {
294            " /*from library*/"
295        } else if !global.must_generate() {
296            " /*not generated*/"
297        } else {
298            ""
299        };
300        writeln!(self.writer, "global {} {{{aliases}{emission}", global.name)?;
301        self.indentation += 1;
302        for (p, is_const) in std::iter::zip(&global.properties, &global.const_properties) {
303            self.indent()?;
304            writeln!(
305                self.writer,
306                "property <{}> {}; //use={}{}",
307                DisplayType(&p.ty),
308                p.name,
309                p.use_count.get(),
310                if *is_const { "  const" } else { "" }
311            )?;
312        }
313        for c in &global.callbacks {
314            self.indent()?;
315            writeln!(
316                self.writer,
317                "callback {} ({}) -> {};",
318                c.name,
319                c.args.iter().map(|t| DisplayType(t).to_string()).join(", "),
320                DisplayType(&c.ret_ty),
321            )?;
322        }
323        for (p, init) in &global.init_values {
324            self.indent()?;
325            match p {
326                LocalMemberIndex::Property(p) => {
327                    writeln!(
328                        self.writer,
329                        "{}: {}{};",
330                        global.properties[*p].name,
331                        DisplayExpression(&init.expression.borrow(), &ctx,),
332                        if init.kind == super::BindingKind::Constant { "/*const*/" } else { "" }
333                    )?;
334                }
335                LocalMemberIndex::Callback(c) => {
336                    writeln!(
337                        self.writer,
338                        "{} => {};",
339                        global.callbacks[*c].name,
340                        DisplayExpression(&init.expression.borrow(), &ctx,),
341                    )?;
342                }
343                _ => unreachable!(),
344            }
345        }
346
347        for (p, e) in &global.change_callbacks {
348            self.indent()?;
349            writeln!(
350                self.writer,
351                "changed {} => {};",
352                global.properties[*p].name,
353                DisplayExpression(&e.borrow(), &ctx),
354            )?
355        }
356        for f in &global.functions {
357            self.indent()?;
358            writeln!(
359                self.writer,
360                "function {} ({}) -> {} {{ {} }}; ",
361                f.name,
362                f.args.iter().map(ToString::to_string).join(", "),
363                f.ret_ty,
364                DisplayExpression(&f.code.borrow(), &ctx)
365            )?;
366        }
367        self.indentation -= 1;
368        self.indent()?;
369        writeln!(self.writer, "}}")
370    }
371
372    fn indent(&mut self) -> Result {
373        for _ in 0..self.indentation {
374            self.writer.write_str("    ")?;
375        }
376        Ok(())
377    }
378}
379
380/// Name an item by its tree index, following sub-component instances to
381/// their root item (the index of an element that is itself a component).
382fn item_name_in_tree(
383    root: &CompilationUnit,
384    sc: &super::SubComponent,
385    tree_index: u32,
386) -> Option<String> {
387    if let Some(item) = sc.items.iter().find(|i| i.index_in_tree == tree_index) {
388        return Some(item.name.to_string());
389    }
390    let ssc = sc.sub_components.iter().find(|s| s.index_in_tree == tree_index)?;
391    Some(format!("{}.{}", ssc.name, item_name_in_tree(root, &root.sub_components[ssc.ty], 0)?))
392}
393
394pub struct DisplayPropertyRef<'a, T>(pub &'a MemberReference, pub &'a EvaluationContext<'a, T>);
395impl<T> Display for DisplayPropertyRef<'_, T> {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
397        let ctx = self.1;
398        match &self.0 {
399            MemberReference::Relative { parent_level, local_reference } => {
400                print_local_ref(f, ctx, local_reference, *parent_level)
401            }
402            MemberReference::Global { global_index, member } => {
403                let g = &ctx.compilation_unit.globals[*global_index];
404                match member {
405                    LocalMemberIndex::Property(property_index) => {
406                        write!(f, "{}.{}", g.name, g.properties[*property_index].name)
407                    }
408                    LocalMemberIndex::Callback(callback_index) => {
409                        write!(f, "{}.{}", g.name, g.callbacks[*callback_index].name)
410                    }
411                    LocalMemberIndex::Function(function_index) => {
412                        write!(f, "{}.{}", g.name, g.functions[*function_index].name)
413                    }
414                    _ => write!(f, "<invalid reference in global>"),
415                }
416            }
417        }
418    }
419}
420
421pub struct DisplayLocalRef<'a, T>(pub &'a LocalMemberReference, pub &'a EvaluationContext<'a, T>);
422impl<T> Display for DisplayLocalRef<'_, T> {
423    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
424        print_local_ref(f, self.1, self.0, 0)
425    }
426}
427
428fn print_local_ref<T>(
429    f: &mut std::fmt::Formatter<'_>,
430    ctx: &EvaluationContext<T>,
431    local_ref: &LocalMemberReference,
432    parent_level: usize,
433) -> Result {
434    if let Some(g) = ctx.current_global() {
435        match &local_ref.reference {
436            LocalMemberIndex::Property(property_index) => {
437                write!(f, "{}.{}", g.name, g.properties[*property_index].name)
438            }
439            LocalMemberIndex::Callback(callback_index) => {
440                write!(f, "{}.{}", g.name, g.callbacks[*callback_index].name)
441            }
442            LocalMemberIndex::Function(function_index) => {
443                write!(f, "{}.{}", g.name, g.functions[*function_index].name)
444            }
445            _ => write!(f, "<invalid reference in global>"),
446        }
447    } else {
448        let Some(s) = ctx.parent_sub_component_idx(parent_level) else {
449            return write!(f, "<invalid parent reference>");
450        };
451        let mut sc = &ctx.compilation_unit.sub_components[s];
452
453        for i in &local_ref.sub_component_path {
454            write!(f, "{}.", sc.sub_components[*i].name)?;
455            sc = &ctx.compilation_unit.sub_components[sc.sub_components[*i].ty];
456        }
457        match &local_ref.reference {
458            LocalMemberIndex::Property(property_index) => {
459                write!(f, "{}", sc.properties[*property_index].name)
460            }
461            LocalMemberIndex::Callback(callback_index) => {
462                write!(f, "{}", sc.callbacks[*callback_index].name)
463            }
464            LocalMemberIndex::Function(function_index) => {
465                write!(f, "{}", sc.functions[*function_index].name)
466            }
467            LocalMemberIndex::Native { item_index, prop_name, .. } => {
468                let i = &sc.items[*item_index];
469                write!(f, "{}.{}", i.name, prop_name)
470            }
471            LocalMemberIndex::Timer(timer_index) => {
472                write!(f, "timer#{}", usize::from(*timer_index))
473            }
474        }
475    }
476}
477
478pub struct DisplayExpression<'a, T>(pub &'a Expression, pub &'a EvaluationContext<'a, T>);
479impl<'a, T> Display for DisplayExpression<'a, T> {
480    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result {
481        let ctx = self.1;
482        let e = |e: &'a Expression| DisplayExpression(e, ctx);
483        match self.0 {
484            Expression::StringLiteral(x) => write!(f, "{x:?}"),
485            Expression::NumberLiteral(x) => write!(f, "{x:?}"),
486            Expression::BoolLiteral(x) => write!(f, "{x:?}"),
487            Expression::KeysLiteral(keys) => {
488                write!(f, "@keys({keys})",)
489            }
490            Expression::PropertyReference(x) => write!(f, "{}", DisplayPropertyRef(x, ctx)),
491            Expression::FunctionParameterReference { index } => write!(f, "arg_{index}"),
492            Expression::StoreLocalVariable { name, value } => {
493                write!(f, "{} = {}", name, e(value))
494            }
495            Expression::ReadLocalVariable { name, .. } => write!(f, "{name}"),
496            Expression::StructFieldAccess { base, name } => write!(f, "{}.{}", e(base), name),
497            Expression::ArrayIndex { array, index } => write!(f, "{}[{}]", e(array), e(index)),
498            Expression::Cast { from, to } => write!(f, "{} /*as {:?}*/", e(from), to),
499            Expression::CodeBlock(v) => {
500                write!(f, "{{ {} }}", v.iter().map(e).join("; "))
501            }
502            Expression::BuiltinFunctionCall { function, arguments, .. } => {
503                write!(f, "{:?}({})", function, arguments.iter().map(e).join(", "))
504            }
505            Expression::CallBackCall { callback, arguments } => {
506                write!(
507                    f,
508                    "{}({})",
509                    DisplayPropertyRef(callback, ctx),
510                    arguments.iter().map(e).join(", ")
511                )
512            }
513            Expression::FunctionCall { function, arguments } => {
514                write!(
515                    f,
516                    "{}({})",
517                    DisplayPropertyRef(function, ctx),
518                    arguments.iter().map(e).join(", ")
519                )
520            }
521            Expression::ItemMemberFunctionCall { function } => {
522                write!(f, "{}()", DisplayPropertyRef(function, ctx))
523            }
524            Expression::ExtraBuiltinFunctionCall { function, arguments, .. } => {
525                write!(f, "{}({})", function, arguments.iter().map(e).join(", "))
526            }
527            Expression::PropertyAssignment { property, value } => {
528                write!(f, "{} = {}", DisplayPropertyRef(property, ctx), e(value))
529            }
530            Expression::ModelDataAssignment { level, value } => {
531                write!(f, "data_{} = {}", level, e(value))
532            }
533            Expression::ArrayIndexAssignment { array, index, value } => {
534                write!(f, "{}[{}] = {}", e(array), e(index), e(value))
535            }
536            Expression::SliceIndexAssignment { slice_name, index, value } => {
537                write!(f, "{}[{}] = {}", slice_name, index, e(value))
538            }
539            Expression::BinaryExpression { lhs, rhs, op } => {
540                write!(f, "({} {} {})", e(lhs), op, e(rhs))
541            }
542            Expression::UnaryOp { sub, op } => write!(f, "{}{}", op, e(sub)),
543            Expression::ImageReference { resource_ref, nine_slice } => {
544                write!(f, "{resource_ref:?}")?;
545                if let Some(nine_slice) = &nine_slice {
546                    write!(f, "nine-slice({nine_slice:?})")?;
547                }
548                Ok(())
549            }
550            Expression::Condition { condition, true_expr, false_expr } => {
551                write!(f, "({} ? {} : {})", e(condition), e(true_expr), e(false_expr))
552            }
553            Expression::Array { values, .. } => {
554                write!(f, "[{}]", values.iter().map(e).join(", "))
555            }
556            Expression::Struct { values, .. } => write!(
557                f,
558                "{{ {} }}",
559                values.iter().map(|(k, v)| format!("{}: {}", k, e(v))).join(", ")
560            ),
561            Expression::EasingCurve(x) => write!(f, "{x:?}"),
562            Expression::MouseCursor(x) => write!(f, "{x:?}"),
563            Expression::LinearGradient { angle, stops } => write!(
564                f,
565                "@linear-gradient({}, {})",
566                e(angle),
567                stops.iter().map(|(e1, e2)| format!("{} {}", e(e1), e(e2))).join(", ")
568            ),
569            Expression::RadialGradient { center, radius, stops } => {
570                let center_str = center
571                    .as_ref()
572                    .map(|(cx, cy)| format!(" at {} {}", e(cx), e(cy)))
573                    .unwrap_or_default();
574                let radius_str = radius.as_ref().map(|r| format!(" {}", e(r))).unwrap_or_default();
575                write!(
576                    f,
577                    "@radial-gradient(circle{radius_str}{center_str}, {})",
578                    stops.iter().map(|(e1, e2)| format!("{} {}", e(e1), e(e2))).join(", ")
579                )
580            }
581            Expression::ConicGradient { from_angle, center, stops } => {
582                let center_str = center
583                    .as_ref()
584                    .map(|(cx, cy)| format!(" at {} {}", e(cx), e(cy)))
585                    .unwrap_or_default();
586                write!(
587                    f,
588                    "@conic-gradient(from {}{center_str}, {})",
589                    e(from_angle),
590                    stops.iter().map(|(e1, e2)| format!("{} {}", e(e1), e(e2))).join(", ")
591                )
592            }
593            Expression::EnumerationValue(x) => write!(f, "{x}"),
594            Expression::LayoutCacheAccess {
595                layout_cache_prop,
596                index,
597                repeater_index: None,
598                ..
599            } => {
600                write!(f, "{}[{}]", DisplayPropertyRef(layout_cache_prop, ctx), index)
601            }
602            Expression::LayoutCacheAccess {
603                layout_cache_prop,
604                index,
605                repeater_index: Some(ri),
606                entries_per_item,
607            } => {
608                write!(
609                    f,
610                    "{0}[{0}[{1}] + {2} * {3}]",
611                    DisplayPropertyRef(layout_cache_prop, ctx),
612                    index,
613                    e(ri),
614                    entries_per_item
615                )
616            }
617            Expression::GridRepeaterCacheAccess {
618                layout_cache_prop,
619                index,
620                repeater_index,
621                stride,
622                child_offset,
623                inner_repeater_index,
624                entries_per_item,
625            } => {
626                if let Some(inner_idx) = inner_repeater_index {
627                    write!(
628                        f,
629                        "{0}[{0}[{1}] + {2} * {3} + {4} * {5} + {6}]",
630                        DisplayPropertyRef(layout_cache_prop, ctx),
631                        index,
632                        e(repeater_index),
633                        e(stride),
634                        e(inner_idx),
635                        entries_per_item,
636                        child_offset
637                    )
638                } else {
639                    write!(
640                        f,
641                        "{0}[{0}[{1}] + {2} * {3} + {4}]",
642                        DisplayPropertyRef(layout_cache_prop, ctx),
643                        index,
644                        e(repeater_index),
645                        e(stride),
646                        child_offset
647                    )
648                }
649            }
650            Expression::WithLayoutItemInfo {
651                cells_variable,
652                repeater_indices_var_name,
653                repeater_steps_var_name,
654                elements,
655                orientation,
656                repeated_cross_size,
657                sub_expression,
658            } => {
659                write!(
660                    f,
661                    "{{ {} = [{}] /*{}*/; ",
662                    cells_variable,
663                    elements
664                        .iter()
665                        .map(|x| match x {
666                            Either::Left(x) => e(x).to_string(),
667                            Either::Right(r) => match &r.cross_width {
668                                Some(w) => format!(
669                                    "@repeater({} at cross-width {})",
670                                    usize::from(r.repeater_index),
671                                    e(w)
672                                ),
673                                None => format!("@repeater({})", usize::from(r.repeater_index)),
674                            },
675                        })
676                        .join(", "),
677                    match orientation {
678                        Orientation::Horizontal => "horizontal",
679                        Orientation::Vertical => "vertical",
680                    }
681                )?;
682                if let Some(v) = repeater_indices_var_name {
683                    write!(f, "{v} = @repeater-indices; ")?;
684                }
685                if let Some(v) = repeater_steps_var_name {
686                    write!(f, "{v} = @repeater-steps; ")?;
687                }
688                if let Some(s) = repeated_cross_size {
689                    write!(f, "@repeated-cross-size = {}; ", e(s))?;
690                }
691                write!(f, "{} }}", e(sub_expression))
692            }
693            Expression::WithFlexboxLayoutItemInfo { .. } => {
694                write!(f, "WithFlexboxLayoutItemInfo(TODO)",)
695            }
696            Expression::BoxLayoutInfoOrthoWithMeasure { .. } => {
697                write!(f, "BoxLayoutInfoOrthoWithMeasure(TODO)",)
698            }
699            Expression::FlexboxLayoutInfoCrossAxisWithMeasure { .. } => {
700                write!(f, "FlexboxLayoutInfoCrossAxisWithMeasure(TODO)",)
701            }
702            Expression::SolveFlexboxLayoutWithMeasure { .. } => {
703                write!(f, "SolveFlexboxLayoutWithMeasure(TODO)",)
704            }
705            Expression::WithGridInputData { .. } => write!(f, "WithGridInputData(TODO)",),
706            Expression::MinMax { ty: _, op, lhs, rhs } => match op {
707                MinMaxOp::Min => write!(f, "min({}, {})", e(lhs), e(rhs)),
708                MinMaxOp::Max => write!(f, "max({}, {})", e(lhs), e(rhs)),
709            },
710            Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>",),
711            Expression::EmptyDataTransfer => write!(f, "<empty-data-transfer>",),
712            Expression::TranslationReference { format_args, string_index, plural } => {
713                match plural {
714                    Some(plural) => write!(
715                        f,
716                        "@tr({:?} % {}, {})",
717                        string_index,
718                        DisplayExpression(plural, ctx),
719                        DisplayExpression(format_args, ctx)
720                    ),
721                    None => write!(
722                        f,
723                        "@tr({:?}, {})",
724                        string_index,
725                        DisplayExpression(format_args, ctx)
726                    ),
727                }
728            }
729            Expression::Closure { arg_name, expression } => {
730                let display_name = arg_name.strip_prefix("local_").unwrap_or(arg_name);
731                write!(f, "({}) => {}", display_name, e(expression))
732            }
733            Expression::DebugHook { expression, id } => {
734                write!(f, "debug-hook({id:?}, {})", DisplayExpression(expression, ctx))
735            }
736        }
737    }
738}