sixtyfps_compilerlib/
builtin_macros.rs

1// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
2// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
3
4//! This module contains the implementation of the builtin macros.
5//! They are just transformations that convert into some more complicated expression tree
6
7use crate::diagnostics::{BuildDiagnostics, Spanned};
8use crate::expression_tree::{
9    BuiltinFunction, BuiltinMacroFunction, EasingCurve, Expression, Unit,
10};
11use crate::langtype::{EnumerationValue, Type};
12use crate::parser::NodeOrToken;
13
14/// Used for uniquely name some variables
15static COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(1);
16
17/// "Expand" the macro `mac` (at location `n`) with the arguments `sub_expr`
18pub fn lower_macro(
19    mac: BuiltinMacroFunction,
20    n: Option<NodeOrToken>,
21    mut sub_expr: impl Iterator<Item = (Expression, Option<NodeOrToken>)>,
22    diag: &mut BuildDiagnostics,
23) -> Expression {
24    match mac {
25        BuiltinMacroFunction::Min => min_max_macro(n, '<', sub_expr.collect(), diag),
26        BuiltinMacroFunction::Max => min_max_macro(n, '>', sub_expr.collect(), diag),
27        BuiltinMacroFunction::Debug => debug_macro(n, sub_expr.collect(), diag),
28        BuiltinMacroFunction::CubicBezier => {
29            let mut has_error = None;
30            // FIXME: this is not pretty to be handling there.
31            // Maybe "cubic_bezier" should be a function that is lowered later
32            let mut a = || match sub_expr.next() {
33                None => {
34                    has_error.get_or_insert((n.clone(), "Not enough arguments"));
35                    0.
36                }
37                Some((Expression::NumberLiteral(val, Unit::None), _)) => val as f32,
38                Some((_, n)) => {
39                    has_error.get_or_insert((
40                        n,
41                        "Arguments to cubic bezier curve must be number literal",
42                    ));
43                    0.
44                }
45            };
46            let expr = Expression::EasingCurve(EasingCurve::CubicBezier(a(), a(), a(), a()));
47            if let Some((_, n)) = sub_expr.next() {
48                has_error.get_or_insert((n, "Too many argument for bezier curve"));
49            }
50            if let Some((n, msg)) = has_error {
51                diag.push_error(msg.into(), &n);
52            }
53
54            expr
55        }
56        BuiltinMacroFunction::Rgb => rgb_macro(n, sub_expr.collect(), diag),
57    }
58}
59
60fn min_max_macro(
61    node: Option<NodeOrToken>,
62    op: char,
63    args: Vec<(Expression, Option<NodeOrToken>)>,
64    diag: &mut BuildDiagnostics,
65) -> Expression {
66    if args.is_empty() {
67        diag.push_error("Needs at least one argument".into(), &node);
68        return Expression::Invalid;
69    }
70    let mut args = args.into_iter();
71    let (mut base, arg_node) = args.next().unwrap();
72    let ty = match base.ty() {
73        Type::Float32 => Type::Float32,
74        // In case there are other floats, we don't want to convert the result to int
75        Type::Int32 => Type::Float32,
76        Type::PhysicalLength => Type::PhysicalLength,
77        Type::LogicalLength => Type::LogicalLength,
78        Type::Duration => Type::Duration,
79        Type::Angle => Type::Angle,
80        Type::Percent => Type::Float32,
81        _ => {
82            diag.push_error("Invalid argument type".into(), &arg_node);
83            return Expression::Invalid;
84        }
85    };
86    for (next, arg_node) in args {
87        let rhs = next.maybe_convert_to(ty.clone(), &arg_node, diag);
88        base = min_max_expression(base, rhs, op);
89    }
90    base
91}
92
93fn rgb_macro(
94    node: Option<NodeOrToken>,
95    args: Vec<(Expression, Option<NodeOrToken>)>,
96    diag: &mut BuildDiagnostics,
97) -> Expression {
98    if args.len() < 3 {
99        diag.push_error("Needs 3 or 4 argument".into(), &node);
100        return Expression::Invalid;
101    }
102    let mut arguments: Vec<_> = args
103        .into_iter()
104        .enumerate()
105        .map(|(i, (expr, n))| {
106            if i < 3 {
107                if expr.ty() == Type::Percent {
108                    Expression::BinaryExpression {
109                        lhs: Box::new(expr.maybe_convert_to(Type::Float32, &n, diag)),
110                        rhs: Box::new(Expression::NumberLiteral(255., Unit::None)),
111                        op: '*',
112                    }
113                } else {
114                    expr.maybe_convert_to(Type::Int32, &n, diag)
115                }
116            } else {
117                expr.maybe_convert_to(Type::Float32, &n, diag)
118            }
119        })
120        .collect();
121    if arguments.len() < 4 {
122        arguments.push(Expression::NumberLiteral(1., Unit::None))
123    }
124    Expression::FunctionCall {
125        function: Box::new(Expression::BuiltinFunctionReference(
126            BuiltinFunction::Rgb,
127            node.as_ref().map(|t| t.to_source_location()),
128        )),
129        arguments,
130        source_location: Some(node.to_source_location()),
131    }
132}
133
134fn debug_macro(
135    node: Option<NodeOrToken>,
136    args: Vec<(Expression, Option<NodeOrToken>)>,
137    diag: &mut BuildDiagnostics,
138) -> Expression {
139    let mut string = None;
140    for (expr, node) in args {
141        let val = to_debug_string(expr, node, diag);
142        string = Some(match string {
143            None => val,
144            Some(string) => Expression::BinaryExpression {
145                lhs: Box::new(string),
146                op: '+',
147                rhs: Box::new(Expression::BinaryExpression {
148                    lhs: Box::new(Expression::StringLiteral(", ".into())),
149                    op: '+',
150                    rhs: Box::new(val),
151                }),
152            },
153        });
154    }
155    let sl = node.map(|node| node.to_source_location());
156    Expression::FunctionCall {
157        function: Box::new(Expression::BuiltinFunctionReference(
158            BuiltinFunction::Debug,
159            sl.clone(),
160        )),
161        arguments: vec![string.unwrap_or_else(|| Expression::default_value_for_type(&Type::String))],
162        source_location: sl,
163    }
164}
165
166fn to_debug_string(
167    expr: Expression,
168    node: Option<NodeOrToken>,
169    diag: &mut BuildDiagnostics,
170) -> Expression {
171    let ty = expr.ty();
172    match &ty {
173        Type::Invalid => Expression::Invalid,
174        Type::Void
175        | Type::InferredCallback
176        | Type::InferredProperty
177        | Type::Component(_)
178        | Type::Builtin(_)
179        | Type::Native(_)
180        | Type::Callback { .. }
181        | Type::Function { .. }
182        | Type::ElementReference
183        | Type::LayoutCache
184        | Type::Model
185        | Type::PathData => {
186            diag.push_error("Cannot debug this expression".into(), &node);
187            Expression::Invalid
188        }
189        Type::Float32 | Type::Int32 => expr.maybe_convert_to(Type::String, &node, diag),
190        Type::String => expr,
191        // TODO
192        Type::Color | Type::Brush | Type::Image | Type::Easing | Type::Array(_) => {
193            Expression::StringLiteral("<debug-of-this-type-not-yet-implemented>".into())
194        }
195        Type::Duration
196        | Type::PhysicalLength
197        | Type::LogicalLength
198        | Type::Angle
199        | Type::Percent
200        | Type::UnitProduct(_) => Expression::BinaryExpression {
201            lhs: Box::new(
202                Expression::Cast { from: Box::new(expr), to: Type::Float32 }.maybe_convert_to(
203                    Type::String,
204                    &node,
205                    diag,
206                ),
207            ),
208            op: '+',
209            rhs: Box::new(Expression::StringLiteral(
210                Type::UnitProduct(ty.as_unit_product().unwrap()).to_string(),
211            )),
212        },
213        Type::Bool => Expression::Condition {
214            condition: Box::new(expr),
215            true_expr: Box::new(Expression::StringLiteral("true".into())),
216            false_expr: Box::new(Expression::StringLiteral("false".into())),
217        },
218        Type::Struct { fields, .. } => {
219            let local_object = format!(
220                "debug_struct{}",
221                COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
222            );
223            let mut string = None;
224            for k in fields.keys() {
225                let field_name =
226                    if string.is_some() { format!(", {}: ", k) } else { format!("{{ {}: ", k) };
227                let value = to_debug_string(
228                    Expression::StructFieldAccess {
229                        base: Box::new(Expression::ReadLocalVariable {
230                            name: local_object.clone(),
231                            ty: ty.clone(),
232                        }),
233                        name: k.clone(),
234                    },
235                    node.clone(),
236                    diag,
237                );
238                let field = Expression::BinaryExpression {
239                    lhs: Box::new(Expression::StringLiteral(field_name)),
240                    op: '+',
241                    rhs: Box::new(value),
242                };
243                string = Some(match string {
244                    None => field,
245                    Some(x) => Expression::BinaryExpression {
246                        lhs: Box::new(x),
247                        op: '+',
248                        rhs: Box::new(field),
249                    },
250                });
251            }
252            match string {
253                None => Expression::StringLiteral("{}".into()),
254                Some(string) => Expression::CodeBlock(vec![
255                    Expression::StoreLocalVariable { name: local_object, value: Box::new(expr) },
256                    Expression::BinaryExpression {
257                        lhs: Box::new(string),
258                        op: '+',
259                        rhs: Box::new(Expression::StringLiteral(" }".into())),
260                    },
261                ]),
262            }
263        }
264        Type::Enumeration(enu) => {
265            let local_object = "debug_enum";
266            let mut v = vec![Expression::StoreLocalVariable {
267                name: local_object.into(),
268                value: Box::new(expr),
269            }];
270            let mut cond = Expression::StringLiteral(format!("Error: invalid value for {}", ty));
271            for (idx, val) in enu.values.iter().enumerate() {
272                cond = Expression::Condition {
273                    condition: Box::new(Expression::BinaryExpression {
274                        lhs: Box::new(Expression::ReadLocalVariable {
275                            name: local_object.into(),
276                            ty: ty.clone(),
277                        }),
278                        rhs: Box::new(Expression::EnumerationValue(EnumerationValue {
279                            value: idx,
280                            enumeration: enu.clone(),
281                        })),
282                        op: '=',
283                    }),
284                    true_expr: Box::new(Expression::StringLiteral(val.clone())),
285                    false_expr: Box::new(cond),
286                };
287            }
288            v.push(cond);
289            Expression::CodeBlock(v)
290        }
291    }
292}
293
294/// Generate an expression which is like `min(lhs, rhs)` if op is '<' or `max(lhs, rhs)` if op is '>'.
295/// counter is an unique id.
296/// The rhs and lhs of the expression must have the same numerical type
297pub fn min_max_expression(lhs: Expression, rhs: Expression, op: char) -> Expression {
298    let ty = lhs.ty();
299    let id = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
300    let n1 = format!("minmax_lhs{}", id);
301    let n2 = format!("minmax_rhs{}", id);
302    let a1 = Box::new(Expression::ReadLocalVariable { name: n1.clone(), ty: ty.clone() });
303    let a2 = Box::new(Expression::ReadLocalVariable { name: n2.clone(), ty });
304    Expression::CodeBlock(vec![
305        Expression::StoreLocalVariable { name: n1, value: Box::new(lhs) },
306        Expression::StoreLocalVariable { name: n2, value: Box::new(rhs) },
307        Expression::Condition {
308            condition: Box::new(Expression::BinaryExpression {
309                lhs: a1.clone(),
310                rhs: a2.clone(),
311                op,
312            }),
313            true_expr: a1,
314            false_expr: a2,
315        },
316    ])
317}