sixtyfps_compilerlib/
expression_tree.rs

1// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
2// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
3
4use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned};
5use crate::langtype::{BuiltinElement, EnumerationValue, Type};
6use crate::layout::Orientation;
7use crate::object_tree::*;
8use crate::parser::{NodeOrToken, SyntaxNode};
9use core::cell::RefCell;
10use std::cell::Cell;
11use std::collections::HashMap;
12use std::rc::{Rc, Weak};
13
14// FIXME remove the pub
15pub use crate::namedreference::NamedReference;
16
17#[derive(Debug, Clone, Copy)]
18/// A function built into the run-time
19pub enum BuiltinFunction {
20    GetWindowScaleFactor,
21    Debug,
22    Mod,
23    Round,
24    Ceil,
25    Floor,
26    Abs,
27    Sqrt,
28    Cos,
29    Sin,
30    Tan,
31    ACos,
32    ASin,
33    ATan,
34    Log,
35    Pow,
36    SetFocusItem,
37    ShowPopupWindow,
38    /// the "42".to_float()
39    StringToFloat,
40    /// the "42".is_float()
41    StringIsFloat,
42    ColorBrighter,
43    ColorDarker,
44    ImageSize,
45    ArrayLength,
46    Rgb,
47    ImplicitLayoutInfo(Orientation),
48    RegisterCustomFontByPath,
49    RegisterCustomFontByMemory,
50}
51
52#[derive(Debug, Clone)]
53/// A builtin function which is handled by the compiler pass
54pub enum BuiltinMacroFunction {
55    Min,
56    Max,
57    CubicBezier,
58    Rgb,
59    Debug,
60}
61
62impl BuiltinFunction {
63    pub fn ty(&self) -> Type {
64        match self {
65            BuiltinFunction::GetWindowScaleFactor => Type::Function {
66                return_type: Box::new(Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)])),
67                args: vec![],
68            },
69            BuiltinFunction::Debug => {
70                Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] }
71            }
72            BuiltinFunction::Mod => Type::Function {
73                return_type: Box::new(Type::Int32),
74                args: vec![Type::Int32, Type::Int32],
75            },
76            BuiltinFunction::Round | BuiltinFunction::Ceil | BuiltinFunction::Floor => {
77                Type::Function { return_type: Box::new(Type::Int32), args: vec![Type::Float32] }
78            }
79            BuiltinFunction::Sqrt | BuiltinFunction::Abs => {
80                Type::Function { return_type: Box::new(Type::Float32), args: vec![Type::Float32] }
81            }
82            BuiltinFunction::Cos | BuiltinFunction::Sin | BuiltinFunction::Tan => {
83                Type::Function { return_type: Box::new(Type::Float32), args: vec![Type::Angle] }
84            }
85            BuiltinFunction::ACos | BuiltinFunction::ASin | BuiltinFunction::ATan => {
86                Type::Function { return_type: Box::new(Type::Angle), args: vec![Type::Float32] }
87            }
88            BuiltinFunction::Log | BuiltinFunction::Pow => Type::Function {
89                return_type: Box::new(Type::Float32),
90                args: vec![Type::Float32, Type::Float32],
91            },
92            BuiltinFunction::SetFocusItem => Type::Function {
93                return_type: Box::new(Type::Void),
94                args: vec![Type::ElementReference],
95            },
96            BuiltinFunction::ShowPopupWindow => Type::Function {
97                return_type: Box::new(Type::Void),
98                args: vec![Type::ElementReference],
99            },
100            BuiltinFunction::StringToFloat => {
101                Type::Function { return_type: Box::new(Type::Float32), args: vec![Type::String] }
102            }
103            BuiltinFunction::StringIsFloat => {
104                Type::Function { return_type: Box::new(Type::Bool), args: vec![Type::String] }
105            }
106            BuiltinFunction::ImplicitLayoutInfo(_) => Type::Function {
107                return_type: Box::new(crate::layout::layout_info_type()),
108                args: vec![Type::ElementReference],
109            },
110            BuiltinFunction::ColorBrighter => Type::Function {
111                return_type: Box::new(Type::Color),
112                args: vec![Type::Color, Type::Float32],
113            },
114            BuiltinFunction::ColorDarker => Type::Function {
115                return_type: Box::new(Type::Color),
116                args: vec![Type::Color, Type::Float32],
117            },
118            BuiltinFunction::ImageSize => Type::Function {
119                return_type: Box::new(Type::Struct {
120                    fields: IntoIterator::into_iter([
121                        ("width".to_string(), Type::Int32),
122                        ("height".to_string(), Type::Int32),
123                    ])
124                    .collect(),
125                    name: Some("Size".to_string()),
126                    node: None,
127                }),
128                args: vec![Type::Image],
129            },
130            BuiltinFunction::ArrayLength => {
131                Type::Function { return_type: Box::new(Type::Int32), args: vec![Type::Model] }
132            }
133            BuiltinFunction::Rgb => Type::Function {
134                return_type: Box::new(Type::Color),
135                args: vec![Type::Int32, Type::Int32, Type::Int32, Type::Float32],
136            },
137            BuiltinFunction::RegisterCustomFontByPath => {
138                Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] }
139            }
140            BuiltinFunction::RegisterCustomFontByMemory => {
141                Type::Function { return_type: Box::new(Type::Void), args: vec![Type::Int32] }
142            }
143        }
144    }
145
146    /// It is pure if the return value only depends on its argument and has no side effect
147    fn is_pure(&self) -> bool {
148        match self {
149            BuiltinFunction::GetWindowScaleFactor => false,
150            // Even if it is not pure, we optimize it away anyway
151            BuiltinFunction::Debug => true,
152            BuiltinFunction::Mod
153            | BuiltinFunction::Round
154            | BuiltinFunction::Ceil
155            | BuiltinFunction::Floor
156            | BuiltinFunction::Abs
157            | BuiltinFunction::Sqrt
158            | BuiltinFunction::Cos
159            | BuiltinFunction::Sin
160            | BuiltinFunction::Tan
161            | BuiltinFunction::ACos
162            | BuiltinFunction::ASin
163            | BuiltinFunction::Log
164            | BuiltinFunction::Pow
165            | BuiltinFunction::ATan => true,
166            BuiltinFunction::SetFocusItem => false,
167            BuiltinFunction::ShowPopupWindow => false,
168            BuiltinFunction::StringToFloat | BuiltinFunction::StringIsFloat => true,
169            BuiltinFunction::ColorBrighter | BuiltinFunction::ColorDarker => true,
170            // ImageSize is pure, except when loading images via the network. Then the initial size will be 0/0 and
171            // we need to make sure that calls to this function stay within a binding, so that the property
172            // notification when updating kicks in. Only the online editor (wasm-interpreter) loads images via the network,
173            // which is when this code is targeting wasm.
174            #[cfg(not(target_arch = "wasm32"))]
175            BuiltinFunction::ImageSize => true,
176            #[cfg(target_arch = "wasm32")]
177            BuiltinFunction::ImageSize => false,
178            BuiltinFunction::ArrayLength => true,
179            BuiltinFunction::Rgb => true,
180            BuiltinFunction::ImplicitLayoutInfo(_) => false,
181            BuiltinFunction::RegisterCustomFontByPath
182            | BuiltinFunction::RegisterCustomFontByMemory => false,
183        }
184    }
185}
186
187#[derive(Debug, Clone, Eq, PartialEq)]
188pub enum OperatorClass {
189    ComparisonOp,
190    LogicalOp,
191    ArithmeticOp,
192}
193
194/// the class of for this (binary) operation
195pub fn operator_class(op: char) -> OperatorClass {
196    match op {
197        '=' | '!' | '<' | '>' | '≤' | '≥' => OperatorClass::ComparisonOp,
198        '&' | '|' => OperatorClass::LogicalOp,
199        '+' | '-' | '/' | '*' => OperatorClass::ArithmeticOp,
200        _ => panic!("Invalid operator {:?}", op),
201    }
202}
203
204macro_rules! declare_units {
205    ($( $(#[$m:meta])* $ident:ident = $string:literal -> $ty:ident $(* $factor:expr)? ,)*) => {
206        /// The units that can be used after numbers in the language
207        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
208        pub enum Unit {
209            $($(#[$m])* $ident,)*
210        }
211
212        impl std::fmt::Display for Unit {
213            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214                match self {
215                    $(Self::$ident => write!(f, $string), )*
216                }
217            }
218        }
219
220        impl std::str::FromStr for Unit {
221            type Err = ();
222            fn from_str(s: &str) -> Result<Self, Self::Err> {
223                match s {
224                    $($string => Ok(Self::$ident), )*
225                    _ => Err(())
226                }
227            }
228        }
229
230        impl Unit {
231            pub fn ty(self) -> Type {
232                match self {
233                    $(Self::$ident => Type::$ty, )*
234                }
235            }
236
237            pub fn normalize(self, x: f64) -> f64 {
238                match self {
239                    $(Self::$ident => x $(* $factor as f64)?, )*
240                }
241            }
242
243        }
244    };
245}
246
247declare_units! {
248    /// No unit was given
249    None = "" -> Float32,
250    ///
251    Percent = "%" -> Percent,
252
253    // Lengths or Coord
254
255    /// Physical pixels
256    Phx = "phx" -> PhysicalLength,
257    /// Logical pixels
258    Px = "px" -> LogicalLength,
259    /// Centimeters
260    Cm = "cm" -> LogicalLength * 37.8,
261    /// Millimeters
262    Mm = "mm" -> LogicalLength * 3.78,
263    /// inches
264    In = "in" -> LogicalLength * 96,
265    /// Points
266    Pt = "pt" -> LogicalLength * 96./72.,
267
268    // durations
269
270    /// Seconds
271    S = "s" -> Duration * 1000,
272    /// Milliseconds
273    Ms = "ms" -> Duration,
274
275    // angles
276
277    /// Degree
278    Deg = "deg" -> Angle,
279    /// Gradians
280    Grad = "grad" -> Angle * 360./180.,
281    /// Turns
282    Turn = "turn" -> Angle * 360.,
283    /// Radians
284    Rad = "rad" -> Angle * 360./std::f32::consts::TAU,
285}
286
287impl Default for Unit {
288    fn default() -> Self {
289        Self::None
290    }
291}
292
293/// The Expression is hold by properties, so it should not hold any strong references to node from the object_tree
294#[derive(Debug, Clone)]
295pub enum Expression {
296    /// Something went wrong (and an error will be reported)
297    Invalid,
298    /// We haven't done the lookup yet
299    Uncompiled(SyntaxNode),
300
301    /// A string literal. The .0 is the content of the string, without the quotes
302    StringLiteral(String),
303    /// Number
304    NumberLiteral(f64, Unit),
305    ///
306    BoolLiteral(bool),
307
308    /// Reference to the callback <name> in the <element>
309    ///
310    /// Note: if we are to separate expression and statement, we probably do not need to have callback reference within expressions
311    CallbackReference(NamedReference),
312
313    /// Reference to the callback <name> in the <element>
314    PropertyReference(NamedReference),
315
316    /// Reference to a function built into the run-time, implemented natively
317    BuiltinFunctionReference(BuiltinFunction, Option<SourceLocation>),
318
319    /// A MemberFunction expression exists only for a short time, for example for `item.focus()` to be translated to
320    /// a regular FunctionCall expression where the base becomes the first argument.
321    MemberFunction {
322        base: Box<Expression>,
323        base_node: Option<NodeOrToken>,
324        member: Box<Expression>,
325    },
326
327    /// Reference to a macro understood by the compiler.
328    /// These should be transformed to other expression before reaching generation
329    BuiltinMacroReference(BuiltinMacroFunction, Option<NodeOrToken>),
330
331    /// A reference to a specific element. This isn't possible to create in .60 syntax itself, but intermediate passes may generate this
332    /// type of expression.
333    ElementReference(Weak<RefCell<Element>>),
334
335    /// Reference to the index variable of a repeater
336    ///
337    /// Example: `idx`  in `for xxx[idx] in ...`.   The element is the reference to the
338    /// element that is repeated
339    RepeaterIndexReference {
340        element: Weak<RefCell<Element>>,
341    },
342
343    /// Reference to the model variable of a repeater
344    ///
345    /// Example: `xxx`  in `for xxx[idx] in ...`.   The element is the reference to the
346    /// element that is repeated
347    RepeaterModelReference {
348        element: Weak<RefCell<Element>>,
349    },
350
351    /// Reference the parameter at the given index of the current function.
352    FunctionParameterReference {
353        index: usize,
354        ty: Type,
355    },
356
357    /// Should be directly within a CodeBlock expression, and store the value of the expression in a local variable
358    StoreLocalVariable {
359        name: String,
360        value: Box<Expression>,
361    },
362
363    /// a reference to the local variable with the given name. The type system should ensure that a variable has been stored
364    /// with this name and this type before in one of the statement of an enclosing codeblock
365    ReadLocalVariable {
366        name: String,
367        ty: Type,
368    },
369
370    /// Access to a field of the given name within a struct.
371    StructFieldAccess {
372        /// This expression should have [`Type::Struct`] type
373        base: Box<Expression>,
374        name: String,
375    },
376
377    /// Access to a index within an array.
378    ArrayIndex {
379        /// This expression should have [`Type::Array`] type
380        array: Box<Expression>,
381        index: Box<Expression>,
382    },
383
384    /// Cast an expression to the given type
385    Cast {
386        from: Box<Expression>,
387        to: Type,
388    },
389
390    /// a code block with different expression
391    CodeBlock(Vec<Expression>),
392
393    /// A function call
394    FunctionCall {
395        function: Box<Expression>,
396        arguments: Vec<Expression>,
397        source_location: Option<SourceLocation>,
398    },
399
400    /// A SelfAssignment or an Assignment.  When op is '=' this is a signal assignment.
401    SelfAssignment {
402        lhs: Box<Expression>,
403        rhs: Box<Expression>,
404        /// '+', '-', '/', '*', or '='
405        op: char,
406    },
407
408    BinaryExpression {
409        lhs: Box<Expression>,
410        rhs: Box<Expression>,
411        /// '+', '-', '/', '*', '=', '!', '<', '>', '≤', '≥', '&', '|'
412        op: char,
413    },
414
415    UnaryOp {
416        sub: Box<Expression>,
417        /// '+', '-', '!'
418        op: char,
419    },
420
421    ImageReference {
422        resource_ref: ImageReference,
423        source_location: Option<SourceLocation>,
424    },
425
426    Condition {
427        condition: Box<Expression>,
428        true_expr: Box<Expression>,
429        false_expr: Box<Expression>,
430    },
431
432    Array {
433        element_ty: Type,
434        values: Vec<Expression>,
435    },
436    Struct {
437        ty: Type,
438        values: HashMap<String, Expression>,
439    },
440
441    PathData(Path),
442
443    EasingCurve(EasingCurve),
444
445    LinearGradient {
446        angle: Box<Expression>,
447        /// First expression in the tuple is a color, second expression is the stop position
448        stops: Vec<(Expression, Expression)>,
449    },
450
451    EnumerationValue(EnumerationValue),
452
453    ReturnStatement(Option<Box<Expression>>),
454
455    LayoutCacheAccess {
456        layout_cache_prop: NamedReference,
457        index: usize,
458        /// When set, this is the index within a repeater, and the index is then the location of another offset.
459        /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index]`
460        repeater_index: Option<Box<Expression>>,
461    },
462    /// Compute the LayoutInfo for the given layout.
463    /// The orientation is the orientation of the cache, not the orientation of the layout
464    ComputeLayoutInfo(crate::layout::Layout, crate::layout::Orientation),
465    SolveLayout(crate::layout::Layout, crate::layout::Orientation),
466}
467
468impl Default for Expression {
469    fn default() -> Self {
470        Expression::Invalid
471    }
472}
473
474impl Expression {
475    /// Return the type of this property
476    pub fn ty(&self) -> Type {
477        match self {
478            Expression::Invalid => Type::Invalid,
479            Expression::Uncompiled(_) => Type::Invalid,
480            Expression::StringLiteral(_) => Type::String,
481            Expression::NumberLiteral(_, unit) => unit.ty(),
482            Expression::BoolLiteral(_) => Type::Bool,
483            Expression::CallbackReference(nr) => nr.ty(),
484            Expression::PropertyReference(nr) => nr.ty(),
485            Expression::BuiltinFunctionReference(funcref, _) => funcref.ty(),
486            Expression::MemberFunction { member, .. } => member.ty(),
487            Expression::BuiltinMacroReference { .. } => Type::Invalid, // We don't know the type
488            Expression::ElementReference(_) => Type::ElementReference,
489            Expression::RepeaterIndexReference { .. } => Type::Int32,
490            Expression::RepeaterModelReference { element } => {
491                if let Expression::Cast { from, .. } = element
492                    .upgrade()
493                    .unwrap()
494                    .borrow()
495                    .repeated
496                    .as_ref()
497                    .map_or(&Expression::Invalid, |e| &e.model)
498                {
499                    match from.ty() {
500                        Type::Float32 | Type::Int32 => Type::Int32,
501                        Type::Array(elem) => *elem,
502                        _ => Type::Invalid,
503                    }
504                } else {
505                    Type::Invalid
506                }
507            }
508            Expression::FunctionParameterReference { ty, .. } => ty.clone(),
509            Expression::StructFieldAccess { base, name } => match base.ty() {
510                Type::Struct { fields, .. } => {
511                    fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone()
512                }
513                Type::Component(c) => c.root_element.borrow().lookup_property(name).property_type,
514                _ => Type::Invalid,
515            },
516            Expression::ArrayIndex { array, .. } => match array.ty() {
517                Type::Array(ty) => (*ty).clone(),
518                _ => Type::Invalid,
519            },
520            Expression::Cast { to, .. } => to.clone(),
521            Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()),
522            Expression::FunctionCall { function, .. } => match function.ty() {
523                Type::Function { return_type, .. } => *return_type,
524                Type::Callback { return_type, .. } => return_type.map_or(Type::Void, |x| *x),
525                _ => Type::Invalid,
526            },
527            Expression::SelfAssignment { .. } => Type::Void,
528            Expression::ImageReference { .. } => Type::Image,
529            Expression::Condition { condition: _, true_expr, false_expr } => {
530                let true_type = true_expr.ty();
531                let false_type = false_expr.ty();
532                if true_type == false_type {
533                    true_type
534                } else if true_type == Type::Invalid {
535                    false_type
536                } else if false_type == Type::Invalid {
537                    true_type
538                } else {
539                    Type::Void
540                }
541            }
542            Expression::BinaryExpression { op, lhs, rhs } => {
543                if operator_class(*op) != OperatorClass::ArithmeticOp {
544                    Type::Bool
545                } else if *op == '+' || *op == '-' {
546                    let (rhs_ty, lhs_ty) = (rhs.ty(), lhs.ty());
547                    if rhs_ty == lhs_ty {
548                        rhs_ty
549                    } else {
550                        Type::Invalid
551                    }
552                } else {
553                    debug_assert!(*op == '*' || *op == '/');
554                    let unit_vec = |ty| {
555                        if let Type::UnitProduct(v) = ty {
556                            v
557                        } else if let Some(u) = ty.default_unit() {
558                            vec![(u, 1)]
559                        } else {
560                            vec![]
561                        }
562                    };
563                    let mut l_units = unit_vec(lhs.ty());
564                    let mut r_units = unit_vec(rhs.ty());
565                    if *op == '/' {
566                        for (_, power) in &mut r_units {
567                            *power = -*power;
568                        }
569                    }
570                    for (unit, power) in r_units {
571                        if let Some((_, p)) = l_units.iter_mut().find(|(u, _)| *u == unit) {
572                            *p += power;
573                        } else {
574                            l_units.push((unit, power));
575                        }
576                    }
577
578                    // normalize the vector by removing empty and sorting
579                    l_units.retain(|(_, p)| *p != 0);
580                    l_units.sort_unstable_by(|(u1, p1), (u2, p2)| match p2.cmp(p1) {
581                        std::cmp::Ordering::Equal => u1.cmp(u2),
582                        x => x,
583                    });
584
585                    if l_units.is_empty() {
586                        Type::Float32
587                    } else if l_units.len() == 1 && l_units[0].1 == 1 {
588                        l_units[0].0.ty()
589                    } else {
590                        Type::UnitProduct(l_units)
591                    }
592                }
593            }
594            Expression::UnaryOp { sub, .. } => sub.ty(),
595            Expression::Array { element_ty, .. } => Type::Array(Box::new(element_ty.clone())),
596            Expression::Struct { ty, .. } => ty.clone(),
597            Expression::PathData { .. } => Type::PathData,
598            Expression::StoreLocalVariable { .. } => Type::Void,
599            Expression::ReadLocalVariable { ty, .. } => ty.clone(),
600            Expression::EasingCurve(_) => Type::Easing,
601            Expression::LinearGradient { .. } => Type::Brush,
602            Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()),
603            // invalid because the expression is unreachable
604            Expression::ReturnStatement(_) => Type::Invalid,
605            Expression::LayoutCacheAccess { .. } => Type::LogicalLength,
606            Expression::ComputeLayoutInfo(..) => crate::layout::layout_info_type(),
607            Expression::SolveLayout(..) => Type::LayoutCache,
608        }
609    }
610
611    /// Call the visitor for each sub-expression.  (note: this function does not recurse)
612    pub fn visit(&self, mut visitor: impl FnMut(&Self)) {
613        match self {
614            Expression::Invalid => {}
615            Expression::Uncompiled(_) => {}
616            Expression::StringLiteral(_) => {}
617            Expression::NumberLiteral(_, _) => {}
618            Expression::BoolLiteral(_) => {}
619            Expression::CallbackReference { .. } => {}
620            Expression::PropertyReference { .. } => {}
621            Expression::FunctionParameterReference { .. } => {}
622            Expression::BuiltinFunctionReference { .. } => {}
623            Expression::MemberFunction { base, member, .. } => {
624                visitor(&**base);
625                visitor(&**member);
626            }
627            Expression::BuiltinMacroReference { .. } => {}
628            Expression::ElementReference(_) => {}
629            Expression::StructFieldAccess { base, .. } => visitor(&**base),
630            Expression::ArrayIndex { array, index } => {
631                visitor(&**array);
632                visitor(&**index);
633            }
634            Expression::RepeaterIndexReference { .. } => {}
635            Expression::RepeaterModelReference { .. } => {}
636            Expression::Cast { from, .. } => visitor(&**from),
637            Expression::CodeBlock(sub) => {
638                sub.iter().for_each(visitor);
639            }
640            Expression::FunctionCall { function, arguments, source_location: _ } => {
641                visitor(&**function);
642                arguments.iter().for_each(visitor);
643            }
644            Expression::SelfAssignment { lhs, rhs, .. } => {
645                visitor(&**lhs);
646                visitor(&**rhs);
647            }
648            Expression::ImageReference { .. } => {}
649            Expression::Condition { condition, true_expr, false_expr } => {
650                visitor(&**condition);
651                visitor(&**true_expr);
652                visitor(&**false_expr);
653            }
654            Expression::BinaryExpression { lhs, rhs, .. } => {
655                visitor(&**lhs);
656                visitor(&**rhs);
657            }
658            Expression::UnaryOp { sub, .. } => visitor(&**sub),
659            Expression::Array { values, .. } => {
660                for x in values {
661                    visitor(x);
662                }
663            }
664            Expression::Struct { values, .. } => {
665                for x in values.values() {
666                    visitor(x);
667                }
668            }
669            Expression::PathData(data) => match data {
670                Path::Elements(elements) => {
671                    for element in elements {
672                        element.bindings.values().for_each(|binding| visitor(&binding.borrow()))
673                    }
674                }
675                Path::Events(events, coordinates) => {
676                    events.iter().chain(coordinates.iter()).for_each(visitor);
677                }
678            },
679            Expression::StoreLocalVariable { value, .. } => visitor(&**value),
680            Expression::ReadLocalVariable { .. } => {}
681            Expression::EasingCurve(_) => {}
682            Expression::LinearGradient { angle, stops } => {
683                visitor(angle);
684                for (c, s) in stops {
685                    visitor(c);
686                    visitor(s);
687                }
688            }
689            Expression::EnumerationValue(_) => {}
690            Expression::ReturnStatement(expr) => {
691                expr.as_deref().map(visitor);
692            }
693            Expression::LayoutCacheAccess { repeater_index, .. } => {
694                repeater_index.as_deref().map(visitor);
695            }
696            Expression::ComputeLayoutInfo(..) => {}
697            Expression::SolveLayout(..) => {}
698        }
699    }
700
701    pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) {
702        match self {
703            Expression::Invalid => {}
704            Expression::Uncompiled(_) => {}
705            Expression::StringLiteral(_) => {}
706            Expression::NumberLiteral(_, _) => {}
707            Expression::BoolLiteral(_) => {}
708            Expression::CallbackReference { .. } => {}
709            Expression::PropertyReference { .. } => {}
710            Expression::FunctionParameterReference { .. } => {}
711            Expression::BuiltinFunctionReference { .. } => {}
712            Expression::MemberFunction { base, member, .. } => {
713                visitor(&mut **base);
714                visitor(&mut **member);
715            }
716            Expression::BuiltinMacroReference { .. } => {}
717            Expression::ElementReference(_) => {}
718            Expression::StructFieldAccess { base, .. } => visitor(&mut **base),
719            Expression::ArrayIndex { array, index } => {
720                visitor(&mut **array);
721                visitor(&mut **index);
722            }
723            Expression::RepeaterIndexReference { .. } => {}
724            Expression::RepeaterModelReference { .. } => {}
725            Expression::Cast { from, .. } => visitor(&mut **from),
726            Expression::CodeBlock(sub) => {
727                sub.iter_mut().for_each(visitor);
728            }
729            Expression::FunctionCall { function, arguments, source_location: _ } => {
730                visitor(&mut **function);
731                arguments.iter_mut().for_each(visitor);
732            }
733            Expression::SelfAssignment { lhs, rhs, .. } => {
734                visitor(&mut **lhs);
735                visitor(&mut **rhs);
736            }
737            Expression::ImageReference { .. } => {}
738            Expression::Condition { condition, true_expr, false_expr } => {
739                visitor(&mut **condition);
740                visitor(&mut **true_expr);
741                visitor(&mut **false_expr);
742            }
743            Expression::BinaryExpression { lhs, rhs, .. } => {
744                visitor(&mut **lhs);
745                visitor(&mut **rhs);
746            }
747            Expression::UnaryOp { sub, .. } => visitor(&mut **sub),
748            Expression::Array { values, .. } => {
749                for x in values {
750                    visitor(x);
751                }
752            }
753            Expression::Struct { values, .. } => {
754                for x in values.values_mut() {
755                    visitor(x);
756                }
757            }
758            Expression::PathData(data) => match data {
759                Path::Elements(elements) => {
760                    for element in elements {
761                        element
762                            .bindings
763                            .values_mut()
764                            .for_each(|binding| visitor(&mut binding.borrow_mut()))
765                    }
766                }
767                Path::Events(events, coordinates) => {
768                    events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor);
769                }
770            },
771            Expression::StoreLocalVariable { value, .. } => visitor(&mut **value),
772            Expression::ReadLocalVariable { .. } => {}
773            Expression::EasingCurve(_) => {}
774            Expression::LinearGradient { angle, stops } => {
775                visitor(&mut *angle);
776                for (c, s) in stops {
777                    visitor(c);
778                    visitor(s);
779                }
780            }
781            Expression::EnumerationValue(_) => {}
782            Expression::ReturnStatement(expr) => {
783                expr.as_deref_mut().map(visitor);
784            }
785            Expression::LayoutCacheAccess { repeater_index, .. } => {
786                repeater_index.as_deref_mut().map(visitor);
787            }
788            Expression::ComputeLayoutInfo(..) => {}
789            Expression::SolveLayout(..) => {}
790        }
791    }
792
793    /// Visit itself and each sub expression recursively
794    pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) {
795        visitor(self);
796        self.visit(|e| e.visit_recursive(visitor));
797    }
798
799    /// Visit itself and each sub expression recursively
800    pub fn visit_recursive_mut(&mut self, visitor: &mut dyn FnMut(&mut Self)) {
801        visitor(self);
802        self.visit_mut(|e| e.visit_recursive_mut(visitor));
803    }
804
805    pub fn is_constant(&self) -> bool {
806        match self {
807            Expression::Invalid => true,
808            Expression::Uncompiled(_) => false,
809            Expression::StringLiteral(_) => true,
810            Expression::NumberLiteral(_, _) => true,
811            Expression::BoolLiteral(_) => true,
812            Expression::CallbackReference { .. } => false,
813            Expression::PropertyReference(nr) => nr.is_constant(),
814            Expression::BuiltinFunctionReference(func, _) => func.is_pure(),
815            Expression::MemberFunction { .. } => false,
816            Expression::ElementReference(_) => false,
817            Expression::RepeaterIndexReference { .. } => false,
818            Expression::RepeaterModelReference { .. } => false,
819            Expression::FunctionParameterReference { .. } => false,
820            Expression::BuiltinMacroReference { .. } => true,
821            Expression::StructFieldAccess { base, .. } => base.is_constant(),
822            Expression::ArrayIndex { array, index } => array.is_constant() && index.is_constant(),
823            Expression::Cast { from, .. } => from.is_constant(),
824            Expression::CodeBlock(sub) => sub.len() == 1 && sub.first().unwrap().is_constant(),
825            Expression::FunctionCall { function, arguments, .. } => {
826                // Assume that constant function are, in fact, pure
827                function.is_constant() && arguments.iter().all(|a| a.is_constant())
828            }
829            Expression::SelfAssignment { .. } => false,
830            Expression::ImageReference { .. } => true,
831            Expression::Condition { condition, false_expr, true_expr } => {
832                condition.is_constant() && false_expr.is_constant() && true_expr.is_constant()
833            }
834            Expression::BinaryExpression { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(),
835            Expression::UnaryOp { sub, .. } => sub.is_constant(),
836            Expression::Array { values, .. } => values.iter().all(Expression::is_constant),
837            Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant()),
838            Expression::PathData(data) => {
839                if let Path::Elements(elements) = data {
840                    elements
841                        .iter()
842                        .all(|element| element.bindings.values().all(|v| v.borrow().is_constant()))
843                } else {
844                    true
845                }
846            }
847            Expression::StoreLocalVariable { .. } => false,
848            // we should somehow find out if this is constant or not
849            Expression::ReadLocalVariable { .. } => false,
850            Expression::EasingCurve(_) => true,
851            Expression::LinearGradient { angle, stops } => {
852                angle.is_constant() && stops.iter().all(|(c, s)| c.is_constant() && s.is_constant())
853            }
854            Expression::EnumerationValue(_) => true,
855            Expression::ReturnStatement(expr) => {
856                expr.as_ref().map_or(true, |expr| expr.is_constant())
857            }
858            // TODO:  detect constant property within layouts
859            Expression::LayoutCacheAccess { .. } => false,
860            Expression::ComputeLayoutInfo(..) => false,
861            Expression::SolveLayout(..) => false,
862        }
863    }
864
865    /// Create a conversion node if needed, or throw an error if the type is not matching
866    #[must_use]
867    pub fn maybe_convert_to(
868        self,
869        target_type: Type,
870        node: &impl Spanned,
871        diag: &mut BuildDiagnostics,
872    ) -> Expression {
873        let ty = self.ty();
874        if ty == target_type
875            || target_type == Type::Void
876            || target_type == Type::Invalid
877            || ty == Type::Invalid
878        {
879            self
880        } else if ty.can_convert(&target_type) {
881            let from = match (ty, &target_type) {
882                (Type::Percent, Type::Float32) => Expression::BinaryExpression {
883                    lhs: Box::new(self),
884                    rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)),
885                    op: '*',
886                },
887                (
888                    Type::Struct { fields: ref left, .. },
889                    Type::Struct { fields: right, name, node: n },
890                ) if left != right => {
891                    if let Expression::Struct { mut values, .. } = self {
892                        let mut new_values = HashMap::new();
893                        for (key, ty) in right {
894                            let (key, expression) = values.remove_entry(key).map_or_else(
895                                || (key.clone(), Expression::default_value_for_type(ty)),
896                                |(k, e)| (k, e.maybe_convert_to(ty.clone(), node, diag)),
897                            );
898                            new_values.insert(key, expression);
899                        }
900                        return Expression::Struct { values: new_values, ty: target_type };
901                    }
902                    let var_name = "tmpobj";
903                    let mut new_values = HashMap::new();
904                    for (key, ty) in right {
905                        let expression = if left.contains_key(key) {
906                            Expression::StructFieldAccess {
907                                base: Box::new(Expression::ReadLocalVariable {
908                                    name: var_name.into(),
909                                    ty: Type::Struct {
910                                        fields: left.clone(),
911                                        name: name.clone(),
912                                        node: n.clone(),
913                                    },
914                                }),
915                                name: key.clone(),
916                            }
917                            .maybe_convert_to(ty.clone(), node, diag)
918                        } else {
919                            Expression::default_value_for_type(ty)
920                        };
921                        new_values.insert(key.clone(), expression);
922                    }
923                    return Expression::CodeBlock(vec![
924                        Expression::StoreLocalVariable {
925                            name: var_name.into(),
926                            value: Box::new(self),
927                        },
928                        Expression::Struct { values: new_values, ty: target_type },
929                    ]);
930                }
931                (Type::Struct { .. }, Type::Component(component)) => {
932                    let struct_type_for_component = Type::Struct {
933                        fields: component
934                            .root_element
935                            .borrow()
936                            .property_declarations
937                            .iter()
938                            .map(|(name, prop_decl)| {
939                                (name.clone(), prop_decl.property_type.clone())
940                            })
941                            .collect(),
942                        name: None,
943                        node: None,
944                    };
945                    self.maybe_convert_to(struct_type_for_component, node, diag)
946                }
947                (left, right) => match (left.as_unit_product(), right.as_unit_product()) {
948                    (Some(left), Some(right)) => {
949                        if let Some(power) =
950                            crate::langtype::unit_product_length_conversion(&left, &right)
951                        {
952                            let op = if power < 0 { '*' } else { '/' };
953                            let mut result = self;
954                            for _ in 0..power.abs() {
955                                result = Expression::BinaryExpression {
956                                    lhs: Box::new(result),
957                                    rhs: Box::new(Expression::FunctionCall {
958                                        function: Box::new(Expression::BuiltinFunctionReference(
959                                            BuiltinFunction::GetWindowScaleFactor,
960                                            Some(node.to_source_location()),
961                                        )),
962                                        arguments: vec![],
963                                        source_location: Some(node.to_source_location()),
964                                    }),
965                                    op,
966                                }
967                            }
968                            result
969                        } else {
970                            self
971                        }
972                    }
973                    _ => self,
974                },
975            };
976            Expression::Cast { from: Box::new(from), to: target_type }
977        } else if matches!((&ty, &target_type, &self), (Type::Array(left), Type::Array(right), Expression::Array{..})
978            if left.can_convert(right) || **left == Type::Invalid)
979        {
980            // Special case for converting array literals
981            match (self, target_type) {
982                (Expression::Array { values, .. }, Type::Array(target_type)) => Expression::Array {
983                    values: values
984                        .into_iter()
985                        .map(|e| e.maybe_convert_to((*target_type).clone(), node, diag))
986                        .collect(),
987                    element_ty: *target_type,
988                },
989                _ => unreachable!(),
990            }
991        } else {
992            let mut message = format!("Cannot convert {} to {}", ty, target_type);
993            // Explicit error message for unit conversion
994            if let Some(from_unit) = ty.default_unit() {
995                if matches!(&target_type, Type::Int32 | Type::Float32 | Type::String) {
996                    message = format!(
997                        "{}. Divide by 1{} to convert to a plain number",
998                        message, from_unit
999                    );
1000                }
1001            } else if let Some(to_unit) = target_type.default_unit() {
1002                if matches!(ty, Type::Int32 | Type::Float32) {
1003                    if let Expression::NumberLiteral(value, Unit::None) = self {
1004                        if value == 0. {
1005                            // Allow conversion from literal 0 to any unit
1006                            return Expression::NumberLiteral(0., to_unit);
1007                        }
1008                    }
1009                    message = format!(
1010                        "{}. Use an unit, or multiply by 1{} to convert explicitly",
1011                        message, to_unit
1012                    );
1013                }
1014            }
1015            diag.push_error(message, node);
1016            self
1017        }
1018    }
1019
1020    /// Return the default value for the given type
1021    pub fn default_value_for_type(ty: &Type) -> Expression {
1022        match ty {
1023            Type::Invalid
1024            | Type::Component(_)
1025            | Type::Builtin(_)
1026            | Type::Native(_)
1027            | Type::Callback { .. }
1028            | Type::Function { .. }
1029            | Type::Void
1030            | Type::InferredProperty
1031            | Type::InferredCallback
1032            | Type::ElementReference
1033            | Type::LayoutCache => Expression::Invalid,
1034            Type::Float32 => Expression::NumberLiteral(0., Unit::None),
1035            Type::String => Expression::StringLiteral(String::new()),
1036            Type::Int32 | Type::Color | Type::UnitProduct(_) => Expression::Cast {
1037                from: Box::new(Expression::NumberLiteral(0., Unit::None)),
1038                to: ty.clone(),
1039            },
1040            Type::Duration => Expression::NumberLiteral(0., Unit::Ms),
1041            Type::Angle => Expression::NumberLiteral(0., Unit::Deg),
1042            Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx),
1043            Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px),
1044            Type::Percent => Expression::NumberLiteral(100., Unit::Percent),
1045            Type::Image => Expression::ImageReference {
1046                resource_ref: ImageReference::None,
1047                source_location: None,
1048            },
1049            Type::Bool => Expression::BoolLiteral(false),
1050            Type::Model => Expression::Invalid,
1051            Type::PathData => Expression::PathData(Path::Elements(vec![])),
1052            Type::Array(element_ty) => {
1053                Expression::Array { element_ty: (**element_ty).clone(), values: vec![] }
1054            }
1055            Type::Struct { fields, .. } => Expression::Struct {
1056                ty: ty.clone(),
1057                values: fields
1058                    .iter()
1059                    .map(|(k, v)| (k.clone(), Expression::default_value_for_type(v)))
1060                    .collect(),
1061            },
1062            Type::Easing => Expression::EasingCurve(EasingCurve::default()),
1063            Type::Brush => Expression::Cast {
1064                from: Box::new(Expression::default_value_for_type(&Type::Color)),
1065                to: Type::Brush,
1066            },
1067            Type::Enumeration(enumeration) => {
1068                Expression::EnumerationValue(enumeration.clone().default_value())
1069            }
1070        }
1071    }
1072
1073    /// Try to mark this expression to a lvalue that can be assigned to.
1074    ///
1075    /// Return true if the expression is a "lvalue" that can be used as the left hand side of a `=` or `+=` or similar
1076    pub fn try_set_rw(&mut self) -> bool {
1077        match self {
1078            Expression::PropertyReference(nr) => {
1079                nr.mark_as_set();
1080                true
1081            }
1082            Expression::StructFieldAccess { base, .. } => base.try_set_rw(),
1083            Expression::RepeaterModelReference { .. } => true,
1084            Expression::ArrayIndex { .. } => true,
1085            _ => false,
1086        }
1087    }
1088}
1089
1090/// The expression in the Element::binding hash table
1091#[derive(Debug, Clone, derive_more::Deref, derive_more::DerefMut)]
1092pub struct BindingExpression {
1093    #[deref]
1094    #[deref_mut]
1095    pub expression: Expression,
1096    /// The location of this expression in the source code
1097    pub span: Option<SourceLocation>,
1098    /// How deep is this binding declared in the hierarchy. When two binding are conflicting
1099    /// for the same priority (because of two way binding), the lower priority wins.
1100    /// The priority starts at 1, and each level of inlining adds one to the priority.
1101    /// 0 means the expression was added by some passes and it is not explicit in the source code
1102    pub priority: i32,
1103
1104    pub animation: Option<PropertyAnimation>,
1105
1106    /// The analysis information. None before it is computed
1107    pub analysis: Option<BindingAnalysis>,
1108
1109    /// The properties this expression is aliased with using two way bindings
1110    pub two_way_bindings: Vec<NamedReference>,
1111}
1112
1113impl std::convert::From<Expression> for BindingExpression {
1114    fn from(expression: Expression) -> Self {
1115        Self {
1116            expression,
1117            span: None,
1118            priority: 0,
1119            animation: Default::default(),
1120            analysis: Default::default(),
1121            two_way_bindings: Default::default(),
1122        }
1123    }
1124}
1125
1126impl BindingExpression {
1127    pub fn new_uncompiled(node: SyntaxNode) -> Self {
1128        Self {
1129            expression: Expression::Uncompiled(node.clone()),
1130            span: Some(node.to_source_location()),
1131            priority: 1,
1132            animation: Default::default(),
1133            analysis: Default::default(),
1134            two_way_bindings: Default::default(),
1135        }
1136    }
1137    pub fn new_with_span(expression: Expression, span: SourceLocation) -> Self {
1138        Self {
1139            expression,
1140            span: Some(span),
1141            priority: 0,
1142            animation: Default::default(),
1143            analysis: Default::default(),
1144            two_way_bindings: Default::default(),
1145        }
1146    }
1147
1148    /// Create an expression binding that simply is a two way binding to the other
1149    pub fn new_two_way(other: NamedReference) -> Self {
1150        Self {
1151            expression: Expression::Invalid,
1152            span: None,
1153            priority: 0,
1154            animation: Default::default(),
1155            analysis: Default::default(),
1156            two_way_bindings: vec![other],
1157        }
1158    }
1159
1160    /// Merge the other into this one. Normally, &self is kept intact (has priority)
1161    /// unless the expression is invalid, in which case the other one is taken.
1162    ///
1163    /// Also the animation is taken if the other don't have one, and the two ways binding
1164    /// are taken into account.
1165    ///
1166    /// Returns true if the other expression was taken
1167    pub fn merge_with(&mut self, other: &Self) -> bool {
1168        if self.animation.is_none() {
1169            self.animation = other.animation.clone();
1170        }
1171        self.two_way_bindings.extend_from_slice(&other.two_way_bindings);
1172        if matches!(self.expression, Expression::Invalid) {
1173            self.priority = other.priority;
1174            self.expression = other.expression.clone();
1175            true
1176        } else {
1177            false
1178        }
1179    }
1180
1181    /// returns false if there is no expression or two way binding
1182    pub fn has_binding(&self) -> bool {
1183        !matches!(self.expression, Expression::Invalid) || !self.two_way_bindings.is_empty()
1184    }
1185}
1186
1187impl Spanned for BindingExpression {
1188    fn span(&self) -> crate::diagnostics::Span {
1189        self.span.as_ref().map(|x| x.span()).unwrap_or_default()
1190    }
1191    fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> {
1192        self.span.as_ref().and_then(|x| x.source_file())
1193    }
1194}
1195
1196#[derive(Default, Debug, Clone)]
1197pub struct BindingAnalysis {
1198    /// true if that binding is part of a binding loop that already has been reported.
1199    pub is_in_binding_loop: Cell<bool>,
1200
1201    /// true if the binding is a constant value that can be set without creating a binding at runtime
1202    pub is_const: bool,
1203
1204    /// true if this binding does not depends on the value of property that are set externally.
1205    /// When true, this binding cannot be part of a binding loop involving external components
1206    pub no_external_dependencies: bool,
1207}
1208
1209#[derive(Debug, Clone)]
1210pub enum Path {
1211    Elements(Vec<PathElement>),
1212    Events(Vec<Expression>, Vec<Expression>),
1213}
1214
1215#[derive(Debug, Clone)]
1216pub struct PathElement {
1217    pub element_type: Rc<BuiltinElement>,
1218    pub bindings: BindingsMap,
1219}
1220
1221#[derive(Clone, Debug)]
1222pub enum EasingCurve {
1223    Linear,
1224    CubicBezier(f32, f32, f32, f32),
1225    // CubicBezierNonConst([Box<Expression>; 4]),
1226    // Custom(Box<dyn Fn(f32)->f32>),
1227}
1228
1229impl Default for EasingCurve {
1230    fn default() -> Self {
1231        Self::Linear
1232    }
1233}
1234
1235// The compiler generates ResourceReference::AbsolutePath for all references like @image-url("foo.png")
1236// and the resource lowering path may change this to EmbeddedData if configured.
1237#[derive(Clone, Debug)]
1238pub enum ImageReference {
1239    None,
1240    AbsolutePath(String),
1241    EmbeddedData { resource_id: usize, extension: String },
1242    EmbeddedTexture { resource_id: usize },
1243}
1244
1245/// Print the expression as a .60 code (not necessarily valid .60)
1246pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std::fmt::Result {
1247    match expression {
1248        Expression::Invalid => write!(f, "<invalid>"),
1249        Expression::Uncompiled(u) => write!(f, "{:?}", u),
1250        Expression::StringLiteral(s) => write!(f, "{:?}", s),
1251        Expression::NumberLiteral(vl, unit) => write!(f, "{}{}", vl, unit),
1252        Expression::BoolLiteral(b) => write!(f, "{:?}", b),
1253        Expression::CallbackReference(a) => write!(f, "{:?}", a),
1254        Expression::PropertyReference(a) => write!(f, "{:?}", a),
1255        Expression::BuiltinFunctionReference(a, _) => write!(f, "{:?}", a),
1256        Expression::MemberFunction { base, base_node: _, member } => {
1257            pretty_print(f, base)?;
1258            write!(f, ".")?;
1259            pretty_print(f, member)
1260        }
1261        Expression::BuiltinMacroReference(a, _) => write!(f, "{:?}", a),
1262        Expression::ElementReference(a) => write!(f, "{:?}", a),
1263        Expression::RepeaterIndexReference { element } => {
1264            crate::namedreference::pretty_print_element_ref(f, element)
1265        }
1266        Expression::RepeaterModelReference { element } => {
1267            crate::namedreference::pretty_print_element_ref(f, element)?;
1268            write!(f, ".@model")
1269        }
1270        Expression::FunctionParameterReference { index, ty: _ } => write!(f, "_arg_{}", index),
1271        Expression::StoreLocalVariable { name, value } => {
1272            write!(f, "{} = ", name)?;
1273            pretty_print(f, value)
1274        }
1275        Expression::ReadLocalVariable { name, ty: _ } => write!(f, "{}", name),
1276        Expression::StructFieldAccess { base, name } => {
1277            pretty_print(f, base)?;
1278            write!(f, ".{}", name)
1279        }
1280        Expression::ArrayIndex { array, index } => {
1281            pretty_print(f, array)?;
1282            write!(f, "[")?;
1283            pretty_print(f, index)?;
1284            write!(f, "]")
1285        }
1286        Expression::Cast { from, to } => {
1287            write!(f, "(")?;
1288            pretty_print(f, from)?;
1289            write!(f, "/* as {} */)", to)
1290        }
1291        Expression::CodeBlock(c) => {
1292            write!(f, "{{ ")?;
1293            for e in c {
1294                pretty_print(f, e)?;
1295                write!(f, "; ")?;
1296            }
1297            write!(f, "}}")
1298        }
1299        Expression::FunctionCall { function, arguments, source_location: _ } => {
1300            pretty_print(f, function)?;
1301            write!(f, "(")?;
1302            for e in arguments {
1303                pretty_print(f, e)?;
1304                write!(f, ", ")?;
1305            }
1306            write!(f, ")")
1307        }
1308        Expression::SelfAssignment { lhs, rhs, op } => {
1309            pretty_print(f, lhs)?;
1310            write!(f, " {}= ", if *op == '=' { ' ' } else { *op })?;
1311            pretty_print(f, rhs)
1312        }
1313        Expression::BinaryExpression { lhs, rhs, op } => {
1314            write!(f, "(")?;
1315            pretty_print(f, lhs)?;
1316            match *op {
1317                '=' | '!' => write!(f, " {}= ", op)?,
1318                _ => write!(f, " {} ", op)?,
1319            };
1320            pretty_print(f, rhs)?;
1321            write!(f, ")")
1322        }
1323        Expression::UnaryOp { sub, op } => {
1324            write!(f, "{}", op)?;
1325            pretty_print(f, sub)
1326        }
1327        Expression::ImageReference { resource_ref, .. } => write!(f, "{:?}", resource_ref),
1328        Expression::Condition { condition, true_expr, false_expr } => {
1329            write!(f, "if (")?;
1330            pretty_print(f, condition)?;
1331            write!(f, ") {{ ")?;
1332            pretty_print(f, true_expr)?;
1333            write!(f, " }} else {{ ")?;
1334            pretty_print(f, false_expr)?;
1335            write!(f, " }}")
1336        }
1337        Expression::Array { element_ty: _, values } => {
1338            write!(f, "[")?;
1339            for e in values {
1340                pretty_print(f, e)?;
1341                write!(f, ", ")?;
1342            }
1343            write!(f, "]")
1344        }
1345        Expression::Struct { ty: _, values } => {
1346            write!(f, "{{ ")?;
1347            for (name, e) in values {
1348                write!(f, "{}: ", name)?;
1349                pretty_print(f, e)?;
1350                write!(f, ", ")?;
1351            }
1352            write!(f, " }}")
1353        }
1354        Expression::PathData(data) => write!(f, "{:?}", data),
1355        Expression::EasingCurve(e) => write!(f, "{:?}", e),
1356        Expression::LinearGradient { angle, stops } => {
1357            write!(f, "@linear-gradient(")?;
1358            pretty_print(f, angle)?;
1359            for (c, s) in stops {
1360                write!(f, ", ")?;
1361                pretty_print(f, c)?;
1362                write!(f, "  ")?;
1363                pretty_print(f, s)?;
1364            }
1365            write!(f, ")")
1366        }
1367        Expression::EnumerationValue(e) => match e.enumeration.values.get(e.value as usize) {
1368            Some(val) => write!(f, "{}.{}", e.enumeration.name, val),
1369            None => write!(f, "{}.{}", e.enumeration.name, e.value),
1370        },
1371        Expression::ReturnStatement(e) => {
1372            write!(f, "return ")?;
1373            e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(()))
1374        }
1375        Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => {
1376            write!(
1377                f,
1378                "{:?}[{}{}]",
1379                layout_cache_prop,
1380                index,
1381                if repeater_index.is_some() { " + $index" } else { "" }
1382            )
1383        }
1384        Expression::ComputeLayoutInfo(..) => write!(f, "layout_info(..)"),
1385        Expression::SolveLayout(..) => write!(f, "solve_layout(..)"),
1386    }
1387}