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 | |
4 | use crate::diagnostics::{BuildDiagnostics, SourceLocation, Spanned}; |
5 | use crate::langtype::{BuiltinElement, EnumerationValue, Function, Struct, Type}; |
6 | use crate::layout::Orientation; |
7 | use crate::lookup::LookupCtx; |
8 | use crate::object_tree::*; |
9 | use crate::parser::{NodeOrToken, SyntaxNode}; |
10 | use crate::typeregister; |
11 | use core::cell::RefCell; |
12 | use smol_str::{format_smolstr, SmolStr}; |
13 | use std::cell::Cell; |
14 | use std::collections::HashMap; |
15 | use std::rc::{Rc, Weak}; |
16 | |
17 | // FIXME remove the pub |
18 | pub use crate::namedreference::NamedReference; |
19 | pub use crate::passes::resolving; |
20 | |
21 | #[derive (Debug, Clone, PartialEq, Eq)] |
22 | /// A function built into the run-time |
23 | pub enum BuiltinFunction { |
24 | GetWindowScaleFactor, |
25 | GetWindowDefaultFontSize, |
26 | AnimationTick, |
27 | Debug, |
28 | Mod, |
29 | Round, |
30 | Ceil, |
31 | Floor, |
32 | Abs, |
33 | Sqrt, |
34 | Cos, |
35 | Sin, |
36 | Tan, |
37 | ACos, |
38 | ASin, |
39 | ATan, |
40 | ATan2, |
41 | Log, |
42 | Pow, |
43 | ToFixed, |
44 | ToPrecision, |
45 | SetFocusItem, |
46 | ClearFocusItem, |
47 | ShowPopupWindow, |
48 | ClosePopupWindow, |
49 | /// Show a context popup menu. |
50 | /// Arguments are `(parent, entries, position)` |
51 | /// |
52 | /// The first argument (parent) is a reference to the `ContectMenu` native item |
53 | /// The second argument (entries) can either be of type Array of MenuEntry, or a reference to a MenuItem tree. |
54 | /// When it is a menu item tree, it is a ElementReference to the root of the tree, and in the LLR, a NumberLiteral to an index in [`crate::llr::SubComponent::menu_item_trees`] |
55 | ShowPopupMenu, |
56 | SetSelectionOffsets, |
57 | ItemFontMetrics, |
58 | /// the "42".to_float() |
59 | StringToFloat, |
60 | /// the "42".is_float() |
61 | StringIsFloat, |
62 | /// the "42".is_empty |
63 | StringIsEmpty, |
64 | /// the "42".length |
65 | StringCharacterCount, |
66 | StringToLowercase, |
67 | StringToUppercase, |
68 | ColorRgbaStruct, |
69 | ColorHsvaStruct, |
70 | ColorBrighter, |
71 | ColorDarker, |
72 | ColorTransparentize, |
73 | ColorMix, |
74 | ColorWithAlpha, |
75 | ImageSize, |
76 | ArrayLength, |
77 | Rgb, |
78 | Hsv, |
79 | ColorScheme, |
80 | SupportsNativeMenuBar, |
81 | /// Setup the native menu bar, or the item-tree based menu bar |
82 | /// arguments are: `(ref entries, ref sub-menu, ref activated, item_tree_root?, no_native_menu_bar?)` |
83 | /// The two last arguments are only set if the menu is an item tree, in which case, `item_tree_root` is a reference |
84 | /// to the MenuItem tree root (just like the entries in the [`Self::ShowPopupMenu`] call), and `native_menu_bar` is |
85 | /// is a boolean literal that is true when we shouldn't try to setup the native menu bar. |
86 | /// If we have an item_tree_root, the code will assign the callback handler and properties on the non-native menubar as well |
87 | SetupNativeMenuBar, |
88 | Use24HourFormat, |
89 | MonthDayCount, |
90 | MonthOffset, |
91 | FormatDate, |
92 | DateNow, |
93 | ValidDate, |
94 | ParseDate, |
95 | TextInputFocused, |
96 | SetTextInputFocused, |
97 | ImplicitLayoutInfo(Orientation), |
98 | ItemAbsolutePosition, |
99 | RegisterCustomFontByPath, |
100 | RegisterCustomFontByMemory, |
101 | RegisterBitmapFont, |
102 | Translate, |
103 | UpdateTimers, |
104 | } |
105 | |
106 | #[derive (Debug, Clone)] |
107 | /// A builtin function which is handled by the compiler pass |
108 | /// |
109 | /// Builtin function expect their arguments in one and a specific type, so that's easier |
110 | /// for the generator. Macro however can do some transformation on their argument. |
111 | /// |
112 | pub enum BuiltinMacroFunction { |
113 | /// Transform `min(a, b, c, ..., z)` into a series of conditional expression and comparisons |
114 | Min, |
115 | /// Transform `max(a, b, c, ..., z)` into a series of conditional expression and comparisons |
116 | Max, |
117 | /// Transforms `clamp(v, min, max)` into a series of min/max calls |
118 | Clamp, |
119 | /// Add the right conversion operations so that the return type is the same as the argument type |
120 | Mod, |
121 | /// Add the right conversion operations so that the return type is the same as the argument type |
122 | Abs, |
123 | CubicBezier, |
124 | /// The argument can be r,g,b,a or r,g,b and they can be percentages or integer. |
125 | /// transform the argument so it is always rgb(r, g, b, a) with r, g, b between 0 and 255. |
126 | Rgb, |
127 | Hsv, |
128 | /// transform `debug(a, b, c)` into debug `a + " " + b + " " + c` |
129 | Debug, |
130 | } |
131 | |
132 | macro_rules! declare_builtin_function_types { |
133 | ($( $Name:ident $(($Pattern:tt))? : ($( $Arg:expr ),*) -> $ReturnType:expr $(,)? )*) => { |
134 | #[allow(non_snake_case)] |
135 | pub struct BuiltinFunctionTypes { |
136 | $(pub $Name : Rc<Function>),* |
137 | } |
138 | impl BuiltinFunctionTypes { |
139 | pub fn new() -> Self { |
140 | Self { |
141 | $($Name : Rc::new(Function{ |
142 | args: vec![$($Arg),*], |
143 | return_type: $ReturnType, |
144 | arg_names: vec![], |
145 | })),* |
146 | } |
147 | } |
148 | |
149 | pub fn ty(&self, function: &BuiltinFunction) -> Rc<Function> { |
150 | match function { |
151 | $(BuiltinFunction::$Name $(($Pattern))? => self.$Name.clone()),* |
152 | } |
153 | } |
154 | } |
155 | }; |
156 | } |
157 | |
158 | declare_builtin_function_types!( |
159 | GetWindowScaleFactor: () -> Type::UnitProduct(vec![(Unit::Phx, 1), (Unit::Px, -1)]), |
160 | GetWindowDefaultFontSize: () -> Type::LogicalLength, |
161 | AnimationTick: () -> Type::Duration, |
162 | Debug: (Type::String) -> Type::Void, |
163 | Mod: (Type::Int32, Type::Int32) -> Type::Int32, |
164 | Round: (Type::Float32) -> Type::Int32, |
165 | Ceil: (Type::Float32) -> Type::Int32, |
166 | Floor: (Type::Float32) -> Type::Int32, |
167 | Sqrt: (Type::Float32) -> Type::Float32, |
168 | Abs: (Type::Float32) -> Type::Float32, |
169 | Cos: (Type::Angle) -> Type::Float32, |
170 | Sin: (Type::Angle) -> Type::Float32, |
171 | Tan: (Type::Angle) -> Type::Float32, |
172 | ACos: (Type::Float32) -> Type::Angle, |
173 | ASin: (Type::Float32) -> Type::Angle, |
174 | ATan: (Type::Float32) -> Type::Angle, |
175 | ATan2: (Type::Float32, Type::Float32) -> Type::Angle, |
176 | Log: (Type::Float32, Type::Float32) -> Type::Float32, |
177 | Pow: (Type::Float32, Type::Float32) -> Type::Float32, |
178 | ToFixed: (Type::Float32, Type::Int32) -> Type::String, |
179 | ToPrecision: (Type::Float32, Type::Int32) -> Type::String, |
180 | SetFocusItem: (Type::ElementReference) -> Type::Void, |
181 | ClearFocusItem: (Type::ElementReference) -> Type::Void, |
182 | ShowPopupWindow: (Type::ElementReference) -> Type::Void, |
183 | ClosePopupWindow: (Type::ElementReference) -> Type::Void, |
184 | ShowPopupMenu: (Type::ElementReference, Type::Model, typeregister::logical_point_type()) -> Type::Void, |
185 | SetSelectionOffsets: (Type::ElementReference, Type::Int32, Type::Int32) -> Type::Void, |
186 | ItemFontMetrics: (Type::ElementReference) -> typeregister::font_metrics_type(), |
187 | StringToFloat: (Type::String) -> Type::Float32, |
188 | StringIsFloat: (Type::String) -> Type::Bool, |
189 | StringIsEmpty: (Type::String) -> Type::Bool, |
190 | StringCharacterCount: (Type::String) -> Type::Int32, |
191 | StringToLowercase: (Type::String) -> Type::String, |
192 | StringToUppercase: (Type::String) -> Type::String, |
193 | ImplicitLayoutInfo(..): (Type::ElementReference) -> Type::Struct(typeregister::layout_info_type()), |
194 | ColorRgbaStruct: (Type::Color) -> Type::Struct(Rc::new(Struct { |
195 | fields: IntoIterator::into_iter([ |
196 | (SmolStr::new_static("red" ), Type::Int32), |
197 | (SmolStr::new_static("green" ), Type::Int32), |
198 | (SmolStr::new_static("blue" ), Type::Int32), |
199 | (SmolStr::new_static("alpha" ), Type::Int32), |
200 | ]) |
201 | .collect(), |
202 | name: Some("Color" .into()), |
203 | node: None, |
204 | rust_attributes: None, |
205 | })), |
206 | ColorHsvaStruct: (Type::Color) -> Type::Struct(Rc::new(Struct { |
207 | fields: IntoIterator::into_iter([ |
208 | (SmolStr::new_static("hue" ), Type::Float32), |
209 | (SmolStr::new_static("saturation" ), Type::Float32), |
210 | (SmolStr::new_static("value" ), Type::Float32), |
211 | (SmolStr::new_static("alpha" ), Type::Float32), |
212 | ]) |
213 | .collect(), |
214 | name: Some("Color" .into()), |
215 | node: None, |
216 | rust_attributes: None, |
217 | })), |
218 | ColorBrighter: (Type::Brush, Type::Float32) -> Type::Brush, |
219 | ColorDarker: (Type::Brush, Type::Float32) -> Type::Brush, |
220 | ColorTransparentize: (Type::Brush, Type::Float32) -> Type::Brush, |
221 | ColorWithAlpha: (Type::Brush, Type::Float32) -> Type::Brush, |
222 | ColorMix: (Type::Color, Type::Color, Type::Float32) -> Type::Color, |
223 | ImageSize: (Type::Image) -> Type::Struct(Rc::new(Struct { |
224 | fields: IntoIterator::into_iter([ |
225 | (SmolStr::new_static("width" ), Type::Int32), |
226 | (SmolStr::new_static("height" ), Type::Int32), |
227 | ]) |
228 | .collect(), |
229 | name: Some("Size" .into()), |
230 | node: None, |
231 | rust_attributes: None, |
232 | })), |
233 | ArrayLength: (Type::Model) -> Type::Int32, |
234 | Rgb: (Type::Int32, Type::Int32, Type::Int32, Type::Float32) -> Type::Color, |
235 | Hsv: (Type::Float32, Type::Float32, Type::Float32, Type::Float32) -> Type::Color, |
236 | ColorScheme: () -> Type::Enumeration( |
237 | typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone()), |
238 | ), |
239 | SupportsNativeMenuBar: () -> Type::Bool, |
240 | // entries, sub-menu, activate. But the types here are not accurate. |
241 | SetupNativeMenuBar: (Type::Model, typeregister::noarg_callback_type(), typeregister::noarg_callback_type()) -> Type::Void, |
242 | MonthDayCount: (Type::Int32, Type::Int32) -> Type::Int32, |
243 | MonthOffset: (Type::Int32, Type::Int32) -> Type::Int32, |
244 | FormatDate: (Type::String, Type::Int32, Type::Int32, Type::Int32) -> Type::String, |
245 | TextInputFocused: () -> Type::Bool, |
246 | DateNow: () -> Type::Array(Rc::new(Type::Int32)), |
247 | ValidDate: (Type::String, Type::String) -> Type::Bool, |
248 | ParseDate: (Type::String, Type::String) -> Type::Array(Rc::new(Type::Int32)), |
249 | SetTextInputFocused: (Type::Bool) -> Type::Void, |
250 | ItemAbsolutePosition: (Type::ElementReference) -> typeregister::logical_point_type(), |
251 | RegisterCustomFontByPath: (Type::String) -> Type::Void, |
252 | RegisterCustomFontByMemory: (Type::Int32) -> Type::Void, |
253 | RegisterBitmapFont: (Type::Int32) -> Type::Void, |
254 | // original, context, domain, args |
255 | Translate: (Type::String, Type::String, Type::String, Type::Array(Type::String.into())) -> Type::String, |
256 | Use24HourFormat: () -> Type::Bool, |
257 | UpdateTimers: () -> Type::Void, |
258 | ); |
259 | |
260 | impl BuiltinFunction { |
261 | pub fn ty(&self) -> Rc<Function> { |
262 | thread_local! { |
263 | static TYPES: BuiltinFunctionTypes = BuiltinFunctionTypes::new(); |
264 | } |
265 | TYPES.with(|types| types.ty(self)) |
266 | } |
267 | |
268 | /// It is const if the return value only depends on its argument and has no side effect |
269 | fn is_const(&self) -> bool { |
270 | match self { |
271 | BuiltinFunction::GetWindowScaleFactor => false, |
272 | BuiltinFunction::GetWindowDefaultFontSize => false, |
273 | BuiltinFunction::AnimationTick => false, |
274 | BuiltinFunction::ColorScheme => false, |
275 | BuiltinFunction::SupportsNativeMenuBar => false, |
276 | BuiltinFunction::SetupNativeMenuBar => false, |
277 | BuiltinFunction::MonthDayCount => false, |
278 | BuiltinFunction::MonthOffset => false, |
279 | BuiltinFunction::FormatDate => false, |
280 | BuiltinFunction::DateNow => false, |
281 | BuiltinFunction::ValidDate => false, |
282 | BuiltinFunction::ParseDate => false, |
283 | // Even if it is not pure, we optimize it away anyway |
284 | BuiltinFunction::Debug => true, |
285 | BuiltinFunction::Mod |
286 | | BuiltinFunction::Round |
287 | | BuiltinFunction::Ceil |
288 | | BuiltinFunction::Floor |
289 | | BuiltinFunction::Abs |
290 | | BuiltinFunction::Sqrt |
291 | | BuiltinFunction::Cos |
292 | | BuiltinFunction::Sin |
293 | | BuiltinFunction::Tan |
294 | | BuiltinFunction::ACos |
295 | | BuiltinFunction::ASin |
296 | | BuiltinFunction::Log |
297 | | BuiltinFunction::Pow |
298 | | BuiltinFunction::ATan |
299 | | BuiltinFunction::ATan2 |
300 | | BuiltinFunction::ToFixed |
301 | | BuiltinFunction::ToPrecision => true, |
302 | BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false, |
303 | BuiltinFunction::ShowPopupWindow |
304 | | BuiltinFunction::ClosePopupWindow |
305 | | BuiltinFunction::ShowPopupMenu => false, |
306 | BuiltinFunction::SetSelectionOffsets => false, |
307 | BuiltinFunction::ItemFontMetrics => false, // depends also on Window's font properties |
308 | BuiltinFunction::StringToFloat |
309 | | BuiltinFunction::StringIsFloat |
310 | | BuiltinFunction::StringIsEmpty |
311 | | BuiltinFunction::StringCharacterCount |
312 | | BuiltinFunction::StringToLowercase |
313 | | BuiltinFunction::StringToUppercase => true, |
314 | BuiltinFunction::ColorRgbaStruct |
315 | | BuiltinFunction::ColorHsvaStruct |
316 | | BuiltinFunction::ColorBrighter |
317 | | BuiltinFunction::ColorDarker |
318 | | BuiltinFunction::ColorTransparentize |
319 | | BuiltinFunction::ColorMix |
320 | | BuiltinFunction::ColorWithAlpha => true, |
321 | // ImageSize is pure, except when loading images via the network. Then the initial size will be 0/0 and |
322 | // we need to make sure that calls to this function stay within a binding, so that the property |
323 | // notification when updating kicks in. Only SlintPad (wasm-interpreter) loads images via the network, |
324 | // which is when this code is targeting wasm. |
325 | #[cfg (not(target_arch = "wasm32" ))] |
326 | BuiltinFunction::ImageSize => true, |
327 | #[cfg (target_arch = "wasm32" )] |
328 | BuiltinFunction::ImageSize => false, |
329 | BuiltinFunction::ArrayLength => true, |
330 | BuiltinFunction::Rgb => true, |
331 | BuiltinFunction::Hsv => true, |
332 | BuiltinFunction::SetTextInputFocused => false, |
333 | BuiltinFunction::TextInputFocused => false, |
334 | BuiltinFunction::ImplicitLayoutInfo(_) => false, |
335 | BuiltinFunction::ItemAbsolutePosition => true, |
336 | BuiltinFunction::RegisterCustomFontByPath |
337 | | BuiltinFunction::RegisterCustomFontByMemory |
338 | | BuiltinFunction::RegisterBitmapFont => false, |
339 | BuiltinFunction::Translate => false, |
340 | BuiltinFunction::Use24HourFormat => false, |
341 | BuiltinFunction::UpdateTimers => false, |
342 | } |
343 | } |
344 | |
345 | // It is pure if it has no side effect |
346 | pub fn is_pure(&self) -> bool { |
347 | match self { |
348 | BuiltinFunction::GetWindowScaleFactor => true, |
349 | BuiltinFunction::GetWindowDefaultFontSize => true, |
350 | BuiltinFunction::AnimationTick => true, |
351 | BuiltinFunction::ColorScheme => true, |
352 | BuiltinFunction::SupportsNativeMenuBar => true, |
353 | BuiltinFunction::SetupNativeMenuBar => false, |
354 | BuiltinFunction::MonthDayCount => true, |
355 | BuiltinFunction::MonthOffset => true, |
356 | BuiltinFunction::FormatDate => true, |
357 | BuiltinFunction::DateNow => true, |
358 | BuiltinFunction::ValidDate => true, |
359 | BuiltinFunction::ParseDate => true, |
360 | // Even if it has technically side effect, we still consider it as pure for our purpose |
361 | BuiltinFunction::Debug => true, |
362 | BuiltinFunction::Mod |
363 | | BuiltinFunction::Round |
364 | | BuiltinFunction::Ceil |
365 | | BuiltinFunction::Floor |
366 | | BuiltinFunction::Abs |
367 | | BuiltinFunction::Sqrt |
368 | | BuiltinFunction::Cos |
369 | | BuiltinFunction::Sin |
370 | | BuiltinFunction::Tan |
371 | | BuiltinFunction::ACos |
372 | | BuiltinFunction::ASin |
373 | | BuiltinFunction::Log |
374 | | BuiltinFunction::Pow |
375 | | BuiltinFunction::ATan |
376 | | BuiltinFunction::ATan2 |
377 | | BuiltinFunction::ToFixed |
378 | | BuiltinFunction::ToPrecision => true, |
379 | BuiltinFunction::SetFocusItem | BuiltinFunction::ClearFocusItem => false, |
380 | BuiltinFunction::ShowPopupWindow |
381 | | BuiltinFunction::ClosePopupWindow |
382 | | BuiltinFunction::ShowPopupMenu => false, |
383 | BuiltinFunction::SetSelectionOffsets => false, |
384 | BuiltinFunction::ItemFontMetrics => true, |
385 | BuiltinFunction::StringToFloat |
386 | | BuiltinFunction::StringIsFloat |
387 | | BuiltinFunction::StringIsEmpty |
388 | | BuiltinFunction::StringCharacterCount |
389 | | BuiltinFunction::StringToLowercase |
390 | | BuiltinFunction::StringToUppercase => true, |
391 | BuiltinFunction::ColorRgbaStruct |
392 | | BuiltinFunction::ColorHsvaStruct |
393 | | BuiltinFunction::ColorBrighter |
394 | | BuiltinFunction::ColorDarker |
395 | | BuiltinFunction::ColorTransparentize |
396 | | BuiltinFunction::ColorMix |
397 | | BuiltinFunction::ColorWithAlpha => true, |
398 | BuiltinFunction::ImageSize => true, |
399 | BuiltinFunction::ArrayLength => true, |
400 | BuiltinFunction::Rgb => true, |
401 | BuiltinFunction::Hsv => true, |
402 | BuiltinFunction::ImplicitLayoutInfo(_) => true, |
403 | BuiltinFunction::ItemAbsolutePosition => true, |
404 | BuiltinFunction::SetTextInputFocused => false, |
405 | BuiltinFunction::TextInputFocused => true, |
406 | BuiltinFunction::RegisterCustomFontByPath |
407 | | BuiltinFunction::RegisterCustomFontByMemory |
408 | | BuiltinFunction::RegisterBitmapFont => false, |
409 | BuiltinFunction::Translate => true, |
410 | BuiltinFunction::Use24HourFormat => true, |
411 | BuiltinFunction::UpdateTimers => false, |
412 | } |
413 | } |
414 | } |
415 | |
416 | /// The base of a Expression::FunctionCall |
417 | #[derive (Debug, Clone)] |
418 | pub enum Callable { |
419 | Callback(NamedReference), |
420 | Function(NamedReference), |
421 | Builtin(BuiltinFunction), |
422 | } |
423 | impl Callable { |
424 | pub fn ty(&self) -> Type { |
425 | match self { |
426 | Callable::Callback(nr: &NamedReference) => nr.ty(), |
427 | Callable::Function(nr: &NamedReference) => nr.ty(), |
428 | Callable::Builtin(b: &BuiltinFunction) => Type::Function(b.ty()), |
429 | } |
430 | } |
431 | } |
432 | impl From<BuiltinFunction> for Callable { |
433 | fn from(function: BuiltinFunction) -> Self { |
434 | Self::Builtin(function) |
435 | } |
436 | } |
437 | |
438 | #[derive (Debug, Clone, Eq, PartialEq)] |
439 | pub enum OperatorClass { |
440 | ComparisonOp, |
441 | LogicalOp, |
442 | ArithmeticOp, |
443 | } |
444 | |
445 | /// the class of for this (binary) operation |
446 | pub fn operator_class(op: char) -> OperatorClass { |
447 | match op { |
448 | '=' | '!' | '<' | '>' | '≤' | '≥' => OperatorClass::ComparisonOp, |
449 | '&' | '|' => OperatorClass::LogicalOp, |
450 | '+' | '-' | '/' | '*' => OperatorClass::ArithmeticOp, |
451 | _ => panic!("Invalid operator {op:?}" ), |
452 | } |
453 | } |
454 | |
455 | macro_rules! declare_units { |
456 | ($( $(#[$m:meta])* $ident:ident = $string:literal -> $ty:ident $(* $factor:expr)? ,)*) => { |
457 | /// The units that can be used after numbers in the language |
458 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, strum::EnumIter)] |
459 | pub enum Unit { |
460 | $($(#[$m])* $ident,)* |
461 | } |
462 | |
463 | impl std::fmt::Display for Unit { |
464 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
465 | match self { |
466 | $(Self::$ident => write!(f, $string), )* |
467 | } |
468 | } |
469 | } |
470 | |
471 | impl std::str::FromStr for Unit { |
472 | type Err = (); |
473 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
474 | match s { |
475 | $($string => Ok(Self::$ident), )* |
476 | _ => Err(()) |
477 | } |
478 | } |
479 | } |
480 | |
481 | impl Unit { |
482 | pub fn ty(self) -> Type { |
483 | match self { |
484 | $(Self::$ident => Type::$ty, )* |
485 | } |
486 | } |
487 | |
488 | pub fn normalize(self, x: f64) -> f64 { |
489 | match self { |
490 | $(Self::$ident => x $(* $factor as f64)?, )* |
491 | } |
492 | } |
493 | |
494 | } |
495 | }; |
496 | } |
497 | |
498 | declare_units! { |
499 | /// No unit was given |
500 | None = "" -> Float32, |
501 | /// Percent value |
502 | Percent = "%" -> Percent, |
503 | |
504 | // Lengths or Coord |
505 | |
506 | /// Physical pixels |
507 | Phx = "phx" -> PhysicalLength, |
508 | /// Logical pixels |
509 | Px = "px" -> LogicalLength, |
510 | /// Centimeters |
511 | Cm = "cm" -> LogicalLength * 37.8, |
512 | /// Millimeters |
513 | Mm = "mm" -> LogicalLength * 3.78, |
514 | /// inches |
515 | In = "in" -> LogicalLength * 96, |
516 | /// Points |
517 | Pt = "pt" -> LogicalLength * 96./72., |
518 | /// Logical pixels multiplied with the window's default-font-size |
519 | Rem = "rem" -> Rem, |
520 | |
521 | // durations |
522 | |
523 | /// Seconds |
524 | S = "s" -> Duration * 1000, |
525 | /// Milliseconds |
526 | Ms = "ms" -> Duration, |
527 | |
528 | // angles |
529 | |
530 | /// Degree |
531 | Deg = "deg" -> Angle, |
532 | /// Gradians |
533 | Grad = "grad" -> Angle * 360./180., |
534 | /// Turns |
535 | Turn = "turn" -> Angle * 360., |
536 | /// Radians |
537 | Rad = "rad" -> Angle * 360./std::f32::consts::TAU, |
538 | } |
539 | |
540 | impl Default for Unit { |
541 | fn default() -> Self { |
542 | Self::None |
543 | } |
544 | } |
545 | |
546 | #[derive (Debug, Clone, Copy)] |
547 | pub enum MinMaxOp { |
548 | Min, |
549 | Max, |
550 | } |
551 | |
552 | /// The Expression is hold by properties, so it should not hold any strong references to node from the object_tree |
553 | #[derive (Debug, Clone, Default)] |
554 | pub enum Expression { |
555 | /// Something went wrong (and an error will be reported) |
556 | #[default] |
557 | Invalid, |
558 | /// We haven't done the lookup yet |
559 | Uncompiled(SyntaxNode), |
560 | |
561 | /// A string literal. The .0 is the content of the string, without the quotes |
562 | StringLiteral(SmolStr), |
563 | /// Number |
564 | NumberLiteral(f64, Unit), |
565 | /// Bool |
566 | BoolLiteral(bool), |
567 | |
568 | /// Reference to the property |
569 | PropertyReference(NamedReference), |
570 | |
571 | /// A reference to a specific element. This isn't possible to create in .slint syntax itself, but intermediate passes may generate this |
572 | /// type of expression. |
573 | ElementReference(Weak<RefCell<Element>>), |
574 | |
575 | /// Reference to the index variable of a repeater |
576 | /// |
577 | /// Example: `idx` in `for xxx[idx] in ...`. The element is the reference to the |
578 | /// element that is repeated |
579 | RepeaterIndexReference { |
580 | element: Weak<RefCell<Element>>, |
581 | }, |
582 | |
583 | /// Reference to the model variable of a repeater |
584 | /// |
585 | /// Example: `xxx` in `for xxx[idx] in ...`. The element is the reference to the |
586 | /// element that is repeated |
587 | RepeaterModelReference { |
588 | element: Weak<RefCell<Element>>, |
589 | }, |
590 | |
591 | /// Reference the parameter at the given index of the current function. |
592 | FunctionParameterReference { |
593 | index: usize, |
594 | ty: Type, |
595 | }, |
596 | |
597 | /// Should be directly within a CodeBlock expression, and store the value of the expression in a local variable |
598 | StoreLocalVariable { |
599 | name: SmolStr, |
600 | value: Box<Expression>, |
601 | }, |
602 | |
603 | /// a reference to the local variable with the given name. The type system should ensure that a variable has been stored |
604 | /// with this name and this type before in one of the statement of an enclosing codeblock |
605 | ReadLocalVariable { |
606 | name: SmolStr, |
607 | ty: Type, |
608 | }, |
609 | |
610 | /// Access to a field of the given name within a struct. |
611 | StructFieldAccess { |
612 | /// This expression should have [`Type::Struct`] type |
613 | base: Box<Expression>, |
614 | name: SmolStr, |
615 | }, |
616 | |
617 | /// Access to a index within an array. |
618 | ArrayIndex { |
619 | /// This expression should have [`Type::Array`] type |
620 | array: Box<Expression>, |
621 | index: Box<Expression>, |
622 | }, |
623 | |
624 | /// Cast an expression to the given type |
625 | Cast { |
626 | from: Box<Expression>, |
627 | to: Type, |
628 | }, |
629 | |
630 | /// a code block with different expression |
631 | CodeBlock(Vec<Expression>), |
632 | |
633 | /// A function call |
634 | FunctionCall { |
635 | function: Callable, |
636 | arguments: Vec<Expression>, |
637 | source_location: Option<SourceLocation>, |
638 | }, |
639 | |
640 | /// A SelfAssignment or an Assignment. When op is '=' this is a simple assignment. |
641 | SelfAssignment { |
642 | lhs: Box<Expression>, |
643 | rhs: Box<Expression>, |
644 | /// '+', '-', '/', '*', or '=' |
645 | op: char, |
646 | node: Option<NodeOrToken>, |
647 | }, |
648 | |
649 | BinaryExpression { |
650 | lhs: Box<Expression>, |
651 | rhs: Box<Expression>, |
652 | /// '+', '-', '/', '*', '=', '!', '<', '>', '≤', '≥', '&', '|' |
653 | op: char, |
654 | }, |
655 | |
656 | UnaryOp { |
657 | sub: Box<Expression>, |
658 | /// '+', '-', '!' |
659 | op: char, |
660 | }, |
661 | |
662 | ImageReference { |
663 | resource_ref: ImageReference, |
664 | source_location: Option<SourceLocation>, |
665 | nine_slice: Option<[u16; 4]>, |
666 | }, |
667 | |
668 | Condition { |
669 | condition: Box<Expression>, |
670 | true_expr: Box<Expression>, |
671 | false_expr: Box<Expression>, |
672 | }, |
673 | |
674 | Array { |
675 | element_ty: Type, |
676 | values: Vec<Expression>, |
677 | }, |
678 | Struct { |
679 | ty: Rc<Struct>, |
680 | values: HashMap<SmolStr, Expression>, |
681 | }, |
682 | |
683 | PathData(Path), |
684 | |
685 | EasingCurve(EasingCurve), |
686 | |
687 | LinearGradient { |
688 | angle: Box<Expression>, |
689 | /// First expression in the tuple is a color, second expression is the stop position |
690 | stops: Vec<(Expression, Expression)>, |
691 | }, |
692 | |
693 | RadialGradient { |
694 | /// First expression in the tuple is a color, second expression is the stop position |
695 | stops: Vec<(Expression, Expression)>, |
696 | }, |
697 | |
698 | EnumerationValue(EnumerationValue), |
699 | |
700 | ReturnStatement(Option<Box<Expression>>), |
701 | |
702 | LayoutCacheAccess { |
703 | layout_cache_prop: NamedReference, |
704 | index: usize, |
705 | /// When set, this is the index within a repeater, and the index is then the location of another offset. |
706 | /// So this looks like `layout_cache_prop[layout_cache_prop[index] + repeater_index]` |
707 | repeater_index: Option<Box<Expression>>, |
708 | }, |
709 | /// Compute the LayoutInfo for the given layout. |
710 | /// The orientation is the orientation of the cache, not the orientation of the layout |
711 | ComputeLayoutInfo(crate::layout::Layout, crate::layout::Orientation), |
712 | SolveLayout(crate::layout::Layout, crate::layout::Orientation), |
713 | |
714 | MinMax { |
715 | ty: Type, |
716 | op: MinMaxOp, |
717 | lhs: Box<Expression>, |
718 | rhs: Box<Expression>, |
719 | }, |
720 | |
721 | DebugHook { |
722 | expression: Box<Expression>, |
723 | id: SmolStr, |
724 | }, |
725 | |
726 | EmptyComponentFactory, |
727 | } |
728 | |
729 | impl Expression { |
730 | /// Return the type of this property |
731 | pub fn ty(&self) -> Type { |
732 | match self { |
733 | Expression::Invalid => Type::Invalid, |
734 | Expression::Uncompiled(_) => Type::Invalid, |
735 | Expression::StringLiteral(_) => Type::String, |
736 | Expression::NumberLiteral(_, unit) => unit.ty(), |
737 | Expression::BoolLiteral(_) => Type::Bool, |
738 | Expression::PropertyReference(nr) => nr.ty(), |
739 | Expression::ElementReference(_) => Type::ElementReference, |
740 | Expression::RepeaterIndexReference { .. } => Type::Int32, |
741 | Expression::RepeaterModelReference { element } => element |
742 | .upgrade() |
743 | .unwrap() |
744 | .borrow() |
745 | .repeated |
746 | .as_ref() |
747 | .map_or(Type::Invalid, |e| model_inner_type(&e.model)), |
748 | Expression::FunctionParameterReference { ty, .. } => ty.clone(), |
749 | Expression::StructFieldAccess { base, name } => match base.ty() { |
750 | Type::Struct(s) => s.fields.get(name.as_str()).unwrap_or(&Type::Invalid).clone(), |
751 | _ => Type::Invalid, |
752 | }, |
753 | Expression::ArrayIndex { array, .. } => match array.ty() { |
754 | Type::Array(ty) => (*ty).clone(), |
755 | _ => Type::Invalid, |
756 | }, |
757 | Expression::Cast { to, .. } => to.clone(), |
758 | Expression::CodeBlock(sub) => sub.last().map_or(Type::Void, |e| e.ty()), |
759 | Expression::FunctionCall { function, .. } => match function.ty() { |
760 | Type::Function(f) | Type::Callback(f) => f.return_type.clone(), |
761 | _ => Type::Invalid, |
762 | }, |
763 | Expression::SelfAssignment { .. } => Type::Void, |
764 | Expression::ImageReference { .. } => Type::Image, |
765 | Expression::Condition { condition: _, true_expr, false_expr } => { |
766 | let true_type = true_expr.ty(); |
767 | let false_type = false_expr.ty(); |
768 | if true_type == false_type { |
769 | true_type |
770 | } else if true_type == Type::Invalid { |
771 | false_type |
772 | } else if false_type == Type::Invalid { |
773 | true_type |
774 | } else { |
775 | Type::Void |
776 | } |
777 | } |
778 | Expression::BinaryExpression { op, lhs, rhs } => { |
779 | if operator_class(*op) != OperatorClass::ArithmeticOp { |
780 | Type::Bool |
781 | } else if *op == '+' || *op == '-' { |
782 | let (rhs_ty, lhs_ty) = (rhs.ty(), lhs.ty()); |
783 | if rhs_ty == lhs_ty { |
784 | rhs_ty |
785 | } else { |
786 | Type::Invalid |
787 | } |
788 | } else { |
789 | debug_assert!(*op == '*' || *op == '/' ); |
790 | let unit_vec = |ty| { |
791 | if let Type::UnitProduct(v) = ty { |
792 | v |
793 | } else if let Some(u) = ty.default_unit() { |
794 | vec![(u, 1)] |
795 | } else { |
796 | vec![] |
797 | } |
798 | }; |
799 | let mut l_units = unit_vec(lhs.ty()); |
800 | let mut r_units = unit_vec(rhs.ty()); |
801 | if *op == '/' { |
802 | for (_, power) in &mut r_units { |
803 | *power = -*power; |
804 | } |
805 | } |
806 | for (unit, power) in r_units { |
807 | if let Some((_, p)) = l_units.iter_mut().find(|(u, _)| *u == unit) { |
808 | *p += power; |
809 | } else { |
810 | l_units.push((unit, power)); |
811 | } |
812 | } |
813 | |
814 | // normalize the vector by removing empty and sorting |
815 | l_units.retain(|(_, p)| *p != 0); |
816 | l_units.sort_unstable_by(|(u1, p1), (u2, p2)| match p2.cmp(p1) { |
817 | std::cmp::Ordering::Equal => u1.cmp(u2), |
818 | x => x, |
819 | }); |
820 | |
821 | if l_units.is_empty() { |
822 | Type::Float32 |
823 | } else if l_units.len() == 1 && l_units[0].1 == 1 { |
824 | l_units[0].0.ty() |
825 | } else { |
826 | Type::UnitProduct(l_units) |
827 | } |
828 | } |
829 | } |
830 | Expression::UnaryOp { sub, .. } => sub.ty(), |
831 | Expression::Array { element_ty, .. } => Type::Array(Rc::new(element_ty.clone())), |
832 | Expression::Struct { ty, .. } => ty.clone().into(), |
833 | Expression::PathData { .. } => Type::PathData, |
834 | Expression::StoreLocalVariable { .. } => Type::Void, |
835 | Expression::ReadLocalVariable { ty, .. } => ty.clone(), |
836 | Expression::EasingCurve(_) => Type::Easing, |
837 | Expression::LinearGradient { .. } => Type::Brush, |
838 | Expression::RadialGradient { .. } => Type::Brush, |
839 | Expression::EnumerationValue(value) => Type::Enumeration(value.enumeration.clone()), |
840 | // invalid because the expression is unreachable |
841 | Expression::ReturnStatement(_) => Type::Invalid, |
842 | Expression::LayoutCacheAccess { .. } => Type::LogicalLength, |
843 | Expression::ComputeLayoutInfo(..) => typeregister::layout_info_type().into(), |
844 | Expression::SolveLayout(..) => Type::LayoutCache, |
845 | Expression::MinMax { ty, .. } => ty.clone(), |
846 | Expression::EmptyComponentFactory => Type::ComponentFactory, |
847 | Expression::DebugHook { expression, .. } => expression.ty(), |
848 | } |
849 | } |
850 | |
851 | /// Call the visitor for each sub-expression. (note: this function does not recurse) |
852 | pub fn visit(&self, mut visitor: impl FnMut(&Self)) { |
853 | match self { |
854 | Expression::Invalid => {} |
855 | Expression::Uncompiled(_) => {} |
856 | Expression::StringLiteral(_) => {} |
857 | Expression::NumberLiteral(_, _) => {} |
858 | Expression::BoolLiteral(_) => {} |
859 | Expression::PropertyReference { .. } => {} |
860 | Expression::FunctionParameterReference { .. } => {} |
861 | Expression::ElementReference(_) => {} |
862 | Expression::StructFieldAccess { base, .. } => visitor(base), |
863 | Expression::ArrayIndex { array, index } => { |
864 | visitor(array); |
865 | visitor(index); |
866 | } |
867 | Expression::RepeaterIndexReference { .. } => {} |
868 | Expression::RepeaterModelReference { .. } => {} |
869 | Expression::Cast { from, .. } => visitor(from), |
870 | Expression::CodeBlock(sub) => { |
871 | sub.iter().for_each(visitor); |
872 | } |
873 | Expression::FunctionCall { function: _, arguments, source_location: _ } => { |
874 | arguments.iter().for_each(visitor); |
875 | } |
876 | Expression::SelfAssignment { lhs, rhs, .. } => { |
877 | visitor(lhs); |
878 | visitor(rhs); |
879 | } |
880 | Expression::ImageReference { .. } => {} |
881 | Expression::Condition { condition, true_expr, false_expr } => { |
882 | visitor(condition); |
883 | visitor(true_expr); |
884 | visitor(false_expr); |
885 | } |
886 | Expression::BinaryExpression { lhs, rhs, .. } => { |
887 | visitor(lhs); |
888 | visitor(rhs); |
889 | } |
890 | Expression::UnaryOp { sub, .. } => visitor(sub), |
891 | Expression::Array { values, .. } => { |
892 | for x in values { |
893 | visitor(x); |
894 | } |
895 | } |
896 | Expression::Struct { values, .. } => { |
897 | for x in values.values() { |
898 | visitor(x); |
899 | } |
900 | } |
901 | Expression::PathData(data) => match data { |
902 | Path::Elements(elements) => { |
903 | for element in elements { |
904 | element.bindings.values().for_each(|binding| visitor(&binding.borrow())) |
905 | } |
906 | } |
907 | Path::Events(events, coordinates) => { |
908 | events.iter().chain(coordinates.iter()).for_each(visitor); |
909 | } |
910 | Path::Commands(commands) => visitor(commands), |
911 | }, |
912 | Expression::StoreLocalVariable { value, .. } => visitor(value), |
913 | Expression::ReadLocalVariable { .. } => {} |
914 | Expression::EasingCurve(_) => {} |
915 | Expression::LinearGradient { angle, stops } => { |
916 | visitor(angle); |
917 | for (c, s) in stops { |
918 | visitor(c); |
919 | visitor(s); |
920 | } |
921 | } |
922 | Expression::RadialGradient { stops } => { |
923 | for (c, s) in stops { |
924 | visitor(c); |
925 | visitor(s); |
926 | } |
927 | } |
928 | Expression::EnumerationValue(_) => {} |
929 | Expression::ReturnStatement(expr) => { |
930 | expr.as_deref().map(visitor); |
931 | } |
932 | Expression::LayoutCacheAccess { repeater_index, .. } => { |
933 | repeater_index.as_deref().map(visitor); |
934 | } |
935 | Expression::ComputeLayoutInfo(..) => {} |
936 | Expression::SolveLayout(..) => {} |
937 | Expression::MinMax { lhs, rhs, .. } => { |
938 | visitor(lhs); |
939 | visitor(rhs); |
940 | } |
941 | Expression::EmptyComponentFactory => {} |
942 | Expression::DebugHook { expression, .. } => visitor(expression), |
943 | } |
944 | } |
945 | |
946 | pub fn visit_mut(&mut self, mut visitor: impl FnMut(&mut Self)) { |
947 | match self { |
948 | Expression::Invalid => {} |
949 | Expression::Uncompiled(_) => {} |
950 | Expression::StringLiteral(_) => {} |
951 | Expression::NumberLiteral(_, _) => {} |
952 | Expression::BoolLiteral(_) => {} |
953 | Expression::PropertyReference { .. } => {} |
954 | Expression::FunctionParameterReference { .. } => {} |
955 | Expression::ElementReference(_) => {} |
956 | Expression::StructFieldAccess { base, .. } => visitor(base), |
957 | Expression::ArrayIndex { array, index } => { |
958 | visitor(array); |
959 | visitor(index); |
960 | } |
961 | Expression::RepeaterIndexReference { .. } => {} |
962 | Expression::RepeaterModelReference { .. } => {} |
963 | Expression::Cast { from, .. } => visitor(from), |
964 | Expression::CodeBlock(sub) => { |
965 | sub.iter_mut().for_each(visitor); |
966 | } |
967 | Expression::FunctionCall { function: _, arguments, source_location: _ } => { |
968 | arguments.iter_mut().for_each(visitor); |
969 | } |
970 | Expression::SelfAssignment { lhs, rhs, .. } => { |
971 | visitor(lhs); |
972 | visitor(rhs); |
973 | } |
974 | Expression::ImageReference { .. } => {} |
975 | Expression::Condition { condition, true_expr, false_expr } => { |
976 | visitor(condition); |
977 | visitor(true_expr); |
978 | visitor(false_expr); |
979 | } |
980 | Expression::BinaryExpression { lhs, rhs, .. } => { |
981 | visitor(lhs); |
982 | visitor(rhs); |
983 | } |
984 | Expression::UnaryOp { sub, .. } => visitor(sub), |
985 | Expression::Array { values, .. } => { |
986 | for x in values { |
987 | visitor(x); |
988 | } |
989 | } |
990 | Expression::Struct { values, .. } => { |
991 | for x in values.values_mut() { |
992 | visitor(x); |
993 | } |
994 | } |
995 | Expression::PathData(data) => match data { |
996 | Path::Elements(elements) => { |
997 | for element in elements { |
998 | element |
999 | .bindings |
1000 | .values_mut() |
1001 | .for_each(|binding| visitor(&mut binding.borrow_mut())) |
1002 | } |
1003 | } |
1004 | Path::Events(events, coordinates) => { |
1005 | events.iter_mut().chain(coordinates.iter_mut()).for_each(visitor); |
1006 | } |
1007 | Path::Commands(commands) => visitor(commands), |
1008 | }, |
1009 | Expression::StoreLocalVariable { value, .. } => visitor(value), |
1010 | Expression::ReadLocalVariable { .. } => {} |
1011 | Expression::EasingCurve(_) => {} |
1012 | Expression::LinearGradient { angle, stops } => { |
1013 | visitor(angle); |
1014 | for (c, s) in stops { |
1015 | visitor(c); |
1016 | visitor(s); |
1017 | } |
1018 | } |
1019 | Expression::RadialGradient { stops } => { |
1020 | for (c, s) in stops { |
1021 | visitor(c); |
1022 | visitor(s); |
1023 | } |
1024 | } |
1025 | Expression::EnumerationValue(_) => {} |
1026 | Expression::ReturnStatement(expr) => { |
1027 | expr.as_deref_mut().map(visitor); |
1028 | } |
1029 | Expression::LayoutCacheAccess { repeater_index, .. } => { |
1030 | repeater_index.as_deref_mut().map(visitor); |
1031 | } |
1032 | Expression::ComputeLayoutInfo(..) => {} |
1033 | Expression::SolveLayout(..) => {} |
1034 | Expression::MinMax { lhs, rhs, .. } => { |
1035 | visitor(lhs); |
1036 | visitor(rhs); |
1037 | } |
1038 | Expression::EmptyComponentFactory => {} |
1039 | Expression::DebugHook { expression, .. } => visitor(expression), |
1040 | } |
1041 | } |
1042 | |
1043 | /// Visit itself and each sub expression recursively |
1044 | pub fn visit_recursive(&self, visitor: &mut dyn FnMut(&Self)) { |
1045 | visitor(self); |
1046 | self.visit(|e| e.visit_recursive(visitor)); |
1047 | } |
1048 | |
1049 | /// Visit itself and each sub expression recursively |
1050 | pub fn visit_recursive_mut(&mut self, visitor: &mut dyn FnMut(&mut Self)) { |
1051 | visitor(self); |
1052 | self.visit_mut(|e| e.visit_recursive_mut(visitor)); |
1053 | } |
1054 | |
1055 | pub fn is_constant(&self) -> bool { |
1056 | match self { |
1057 | Expression::Invalid => true, |
1058 | Expression::Uncompiled(_) => false, |
1059 | Expression::StringLiteral(_) => true, |
1060 | Expression::NumberLiteral(_, _) => true, |
1061 | Expression::BoolLiteral(_) => true, |
1062 | Expression::PropertyReference(nr) => nr.is_constant(), |
1063 | Expression::ElementReference(_) => false, |
1064 | Expression::RepeaterIndexReference { .. } => false, |
1065 | Expression::RepeaterModelReference { .. } => false, |
1066 | // Allow functions to be marked as const |
1067 | Expression::FunctionParameterReference { .. } => true, |
1068 | Expression::StructFieldAccess { base, .. } => base.is_constant(), |
1069 | Expression::ArrayIndex { array, index } => array.is_constant() && index.is_constant(), |
1070 | Expression::Cast { from, .. } => from.is_constant(), |
1071 | // This is conservative: the return value is the last expression in the block, but |
1072 | // we kind of mean "pure" here too, so ensure the whole body is OK. |
1073 | Expression::CodeBlock(sub) => sub.iter().all(|s| s.is_constant()), |
1074 | Expression::FunctionCall { function, arguments, .. } => { |
1075 | let is_const = match function { |
1076 | Callable::Builtin(b) => b.is_const(), |
1077 | Callable::Function(nr) => nr.is_constant(), |
1078 | Callable::Callback(..) => false, |
1079 | }; |
1080 | is_const && arguments.iter().all(|a| a.is_constant()) |
1081 | } |
1082 | Expression::SelfAssignment { .. } => false, |
1083 | Expression::ImageReference { .. } => true, |
1084 | Expression::Condition { condition, false_expr, true_expr } => { |
1085 | condition.is_constant() && false_expr.is_constant() && true_expr.is_constant() |
1086 | } |
1087 | Expression::BinaryExpression { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(), |
1088 | Expression::UnaryOp { sub, .. } => sub.is_constant(), |
1089 | // Array will turn into model, and they can't be considered as constant if the model |
1090 | // is used and the model is changed. CF issue #5249 |
1091 | //Expression::Array { values, .. } => values.iter().all(Expression::is_constant), |
1092 | Expression::Array { .. } => false, |
1093 | Expression::Struct { values, .. } => values.iter().all(|(_, v)| v.is_constant()), |
1094 | Expression::PathData(data) => match data { |
1095 | Path::Elements(elements) => elements |
1096 | .iter() |
1097 | .all(|element| element.bindings.values().all(|v| v.borrow().is_constant())), |
1098 | Path::Events(_, _) => true, |
1099 | Path::Commands(_) => false, |
1100 | }, |
1101 | Expression::StoreLocalVariable { value, .. } => value.is_constant(), |
1102 | // We only load what we store, and stores are alredy checked |
1103 | Expression::ReadLocalVariable { .. } => true, |
1104 | Expression::EasingCurve(_) => true, |
1105 | Expression::LinearGradient { angle, stops } => { |
1106 | angle.is_constant() && stops.iter().all(|(c, s)| c.is_constant() && s.is_constant()) |
1107 | } |
1108 | Expression::RadialGradient { stops } => { |
1109 | stops.iter().all(|(c, s)| c.is_constant() && s.is_constant()) |
1110 | } |
1111 | Expression::EnumerationValue(_) => true, |
1112 | Expression::ReturnStatement(expr) => { |
1113 | expr.as_ref().map_or(true, |expr| expr.is_constant()) |
1114 | } |
1115 | // TODO: detect constant property within layouts |
1116 | Expression::LayoutCacheAccess { .. } => false, |
1117 | Expression::ComputeLayoutInfo(..) => false, |
1118 | Expression::SolveLayout(..) => false, |
1119 | Expression::MinMax { lhs, rhs, .. } => lhs.is_constant() && rhs.is_constant(), |
1120 | Expression::EmptyComponentFactory => true, |
1121 | Expression::DebugHook { .. } => false, |
1122 | } |
1123 | } |
1124 | |
1125 | /// Create a conversion node if needed, or throw an error if the type is not matching |
1126 | #[must_use ] |
1127 | pub fn maybe_convert_to( |
1128 | self, |
1129 | target_type: Type, |
1130 | node: &dyn Spanned, |
1131 | diag: &mut BuildDiagnostics, |
1132 | ) -> Expression { |
1133 | let ty = self.ty(); |
1134 | if ty == target_type |
1135 | || target_type == Type::Void |
1136 | || target_type == Type::Invalid |
1137 | || ty == Type::Invalid |
1138 | { |
1139 | self |
1140 | } else if ty.can_convert(&target_type) { |
1141 | let from = match (ty, &target_type) { |
1142 | (Type::Brush, Type::Color) => match self { |
1143 | Expression::LinearGradient { .. } | Expression::RadialGradient { .. } => { |
1144 | let message = format!("Narrowing conversion from {0} to {1}. This can lead to unexpected behavior because the {0} is a gradient" , Type::Brush, Type::Color); |
1145 | diag.push_warning(message, node); |
1146 | self |
1147 | } |
1148 | _ => self, |
1149 | }, |
1150 | (Type::Percent, Type::Float32) => Expression::BinaryExpression { |
1151 | lhs: Box::new(self), |
1152 | rhs: Box::new(Expression::NumberLiteral(0.01, Unit::None)), |
1153 | op: '*' , |
1154 | }, |
1155 | (ref from_ty @ Type::Struct(ref left), Type::Struct(right)) |
1156 | if left.fields != right.fields => |
1157 | { |
1158 | if let Expression::Struct { mut values, .. } = self { |
1159 | let mut new_values = HashMap::new(); |
1160 | for (key, ty) in &right.fields { |
1161 | let (key, expression) = values.remove_entry(key).map_or_else( |
1162 | || (key.clone(), Expression::default_value_for_type(ty)), |
1163 | |(k, e)| (k, e.maybe_convert_to(ty.clone(), node, diag)), |
1164 | ); |
1165 | new_values.insert(key, expression); |
1166 | } |
1167 | return Expression::Struct { values: new_values, ty: right.clone() }; |
1168 | } |
1169 | static COUNT: std::sync::atomic::AtomicUsize = |
1170 | std::sync::atomic::AtomicUsize::new(0); |
1171 | let var_name = format_smolstr!( |
1172 | "tmpobj_conv_ {}" , |
1173 | COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) |
1174 | ); |
1175 | let mut new_values = HashMap::new(); |
1176 | for (key, ty) in &right.fields { |
1177 | let expression = if left.fields.contains_key(key) { |
1178 | Expression::StructFieldAccess { |
1179 | base: Box::new(Expression::ReadLocalVariable { |
1180 | name: var_name.clone(), |
1181 | ty: from_ty.clone(), |
1182 | }), |
1183 | name: key.clone(), |
1184 | } |
1185 | .maybe_convert_to(ty.clone(), node, diag) |
1186 | } else { |
1187 | Expression::default_value_for_type(ty) |
1188 | }; |
1189 | new_values.insert(key.clone(), expression); |
1190 | } |
1191 | return Expression::CodeBlock(vec![ |
1192 | Expression::StoreLocalVariable { name: var_name, value: Box::new(self) }, |
1193 | Expression::Struct { values: new_values, ty: right.clone() }, |
1194 | ]); |
1195 | } |
1196 | (left, right) => match (left.as_unit_product(), right.as_unit_product()) { |
1197 | (Some(left), Some(right)) => { |
1198 | if let Some(conversion_powers) = |
1199 | crate::langtype::unit_product_length_conversion(&left, &right) |
1200 | { |
1201 | let apply_power = |
1202 | |mut result, power: i8, builtin_fn: BuiltinFunction| { |
1203 | let op = if power < 0 { '*' } else { '/' }; |
1204 | for _ in 0..power.abs() { |
1205 | result = Expression::BinaryExpression { |
1206 | lhs: Box::new(result), |
1207 | rhs: Box::new(Expression::FunctionCall { |
1208 | function: Callable::Builtin(builtin_fn.clone()), |
1209 | arguments: vec![], |
1210 | source_location: Some(node.to_source_location()), |
1211 | }), |
1212 | op, |
1213 | } |
1214 | } |
1215 | result |
1216 | }; |
1217 | |
1218 | let mut result = self; |
1219 | |
1220 | if conversion_powers.rem_to_px_power != 0 { |
1221 | result = apply_power( |
1222 | result, |
1223 | conversion_powers.rem_to_px_power, |
1224 | BuiltinFunction::GetWindowDefaultFontSize, |
1225 | ) |
1226 | } |
1227 | if conversion_powers.px_to_phx_power != 0 { |
1228 | result = apply_power( |
1229 | result, |
1230 | conversion_powers.px_to_phx_power, |
1231 | BuiltinFunction::GetWindowScaleFactor, |
1232 | ) |
1233 | } |
1234 | |
1235 | result |
1236 | } else { |
1237 | self |
1238 | } |
1239 | } |
1240 | _ => self, |
1241 | }, |
1242 | }; |
1243 | Expression::Cast { from: Box::new(from), to: target_type } |
1244 | } else if matches!( |
1245 | (&ty, &target_type, &self), |
1246 | (Type::Array(_), Type::Array(_), Expression::Array { .. }) |
1247 | ) { |
1248 | // Special case for converting array literals |
1249 | match (self, target_type) { |
1250 | (Expression::Array { values, .. }, Type::Array(target_type)) => Expression::Array { |
1251 | values: values |
1252 | .into_iter() |
1253 | .map(|e| e.maybe_convert_to((*target_type).clone(), node, diag)) |
1254 | .take_while(|e| !matches!(e, Expression::Invalid)) |
1255 | .collect(), |
1256 | element_ty: (*target_type).clone(), |
1257 | }, |
1258 | _ => unreachable!(), |
1259 | } |
1260 | } else if let (Type::Struct(struct_type), Expression::Struct { values, .. }) = |
1261 | (&target_type, &self) |
1262 | { |
1263 | // Also special case struct literal in case they contain array literal |
1264 | let mut fields = struct_type.fields.clone(); |
1265 | let mut new_values = HashMap::new(); |
1266 | for (f, v) in values { |
1267 | if let Some(t) = fields.remove(f) { |
1268 | new_values.insert(f.clone(), v.clone().maybe_convert_to(t, node, diag)); |
1269 | } else { |
1270 | diag.push_error(format!("Cannot convert {ty} to {target_type}" ), node); |
1271 | return self; |
1272 | } |
1273 | } |
1274 | for (f, t) in fields { |
1275 | new_values.insert(f, Expression::default_value_for_type(&t)); |
1276 | } |
1277 | Expression::Struct { ty: struct_type.clone(), values: new_values } |
1278 | } else { |
1279 | let mut message = format!("Cannot convert {ty} to {target_type}" ); |
1280 | // Explicit error message for unit conversion |
1281 | if let Some(from_unit) = ty.default_unit() { |
1282 | if matches!(&target_type, Type::Int32 | Type::Float32 | Type::String) { |
1283 | message = |
1284 | format!(" {message}. Divide by 1 {from_unit} to convert to a plain number" ); |
1285 | } |
1286 | } else if let Some(to_unit) = target_type.default_unit() { |
1287 | if matches!(ty, Type::Int32 | Type::Float32) { |
1288 | if let Expression::NumberLiteral(value, Unit::None) = self { |
1289 | if value == 0. { |
1290 | // Allow conversion from literal 0 to any unit |
1291 | return Expression::NumberLiteral(0., to_unit); |
1292 | } |
1293 | } |
1294 | message = format!( |
1295 | " {message}. Use an unit, or multiply by 1 {to_unit} to convert explicitly" |
1296 | ); |
1297 | } |
1298 | } |
1299 | diag.push_error(message, node); |
1300 | self |
1301 | } |
1302 | } |
1303 | |
1304 | /// Return the default value for the given type |
1305 | pub fn default_value_for_type(ty: &Type) -> Expression { |
1306 | match ty { |
1307 | Type::Invalid |
1308 | | Type::Callback { .. } |
1309 | | Type::Function { .. } |
1310 | | Type::InferredProperty |
1311 | | Type::InferredCallback |
1312 | | Type::ElementReference |
1313 | | Type::LayoutCache => Expression::Invalid, |
1314 | Type::Void => Expression::CodeBlock(vec![]), |
1315 | Type::Float32 => Expression::NumberLiteral(0., Unit::None), |
1316 | Type::String => Expression::StringLiteral(SmolStr::default()), |
1317 | Type::Int32 | Type::Color | Type::UnitProduct(_) => Expression::Cast { |
1318 | from: Box::new(Expression::NumberLiteral(0., Unit::None)), |
1319 | to: ty.clone(), |
1320 | }, |
1321 | Type::Duration => Expression::NumberLiteral(0., Unit::Ms), |
1322 | Type::Angle => Expression::NumberLiteral(0., Unit::Deg), |
1323 | Type::PhysicalLength => Expression::NumberLiteral(0., Unit::Phx), |
1324 | Type::LogicalLength => Expression::NumberLiteral(0., Unit::Px), |
1325 | Type::Rem => Expression::NumberLiteral(0., Unit::Rem), |
1326 | Type::Percent => Expression::NumberLiteral(100., Unit::Percent), |
1327 | Type::Image => Expression::ImageReference { |
1328 | resource_ref: ImageReference::None, |
1329 | source_location: None, |
1330 | nine_slice: None, |
1331 | }, |
1332 | Type::Bool => Expression::BoolLiteral(false), |
1333 | Type::Model => Expression::Invalid, |
1334 | Type::PathData => Expression::PathData(Path::Elements(vec![])), |
1335 | Type::Array(element_ty) => { |
1336 | Expression::Array { element_ty: (**element_ty).clone(), values: vec![] } |
1337 | } |
1338 | Type::Struct(s) => Expression::Struct { |
1339 | ty: s.clone(), |
1340 | values: s |
1341 | .fields |
1342 | .iter() |
1343 | .map(|(k, v)| (k.clone(), Expression::default_value_for_type(v))) |
1344 | .collect(), |
1345 | }, |
1346 | Type::Easing => Expression::EasingCurve(EasingCurve::default()), |
1347 | Type::Brush => Expression::Cast { |
1348 | from: Box::new(Expression::default_value_for_type(&Type::Color)), |
1349 | to: Type::Brush, |
1350 | }, |
1351 | Type::Enumeration(enumeration) => { |
1352 | Expression::EnumerationValue(enumeration.clone().default_value()) |
1353 | } |
1354 | Type::ComponentFactory => Expression::EmptyComponentFactory, |
1355 | } |
1356 | } |
1357 | |
1358 | /// Try to mark this expression to a lvalue that can be assigned to. |
1359 | /// |
1360 | /// Return true if the expression is a "lvalue" that can be used as the left hand side of a `=` or `+=` or similar |
1361 | pub fn try_set_rw( |
1362 | &mut self, |
1363 | ctx: &mut LookupCtx, |
1364 | what: &'static str, |
1365 | node: &dyn Spanned, |
1366 | ) -> bool { |
1367 | match self { |
1368 | Expression::PropertyReference(nr) => { |
1369 | nr.mark_as_set(); |
1370 | let mut lookup = nr.element().borrow().lookup_property(nr.name()); |
1371 | lookup.is_local_to_component &= ctx.is_local_element(&nr.element()); |
1372 | if lookup.property_visibility == PropertyVisibility::Constexpr { |
1373 | ctx.diag.push_error( |
1374 | "The property must be known at compile time and cannot be changed at runtime" |
1375 | .into(), |
1376 | node, |
1377 | ); |
1378 | false |
1379 | } else if lookup.is_valid_for_assignment() { |
1380 | if !nr |
1381 | .element() |
1382 | .borrow() |
1383 | .property_analysis |
1384 | .borrow() |
1385 | .get(nr.name()) |
1386 | .is_some_and(|d| d.is_linked_to_read_only) |
1387 | { |
1388 | true |
1389 | } else if ctx.is_legacy_component() { |
1390 | ctx.diag.push_warning("Modifying a property that is linked to a read-only property is deprecated" .into(), node); |
1391 | true |
1392 | } else { |
1393 | ctx.diag.push_error( |
1394 | "Cannot modify a property that is linked to a read-only property" |
1395 | .into(), |
1396 | node, |
1397 | ); |
1398 | false |
1399 | } |
1400 | } else if ctx.is_legacy_component() |
1401 | && lookup.property_visibility == PropertyVisibility::Output |
1402 | { |
1403 | ctx.diag |
1404 | .push_warning(format!(" {what} on an output property is deprecated" ), node); |
1405 | true |
1406 | } else { |
1407 | ctx.diag.push_error( |
1408 | format!(" {what} on a {} property" , lookup.property_visibility), |
1409 | node, |
1410 | ); |
1411 | false |
1412 | } |
1413 | } |
1414 | Expression::StructFieldAccess { base, .. } => base.try_set_rw(ctx, what, node), |
1415 | Expression::RepeaterModelReference { .. } => true, |
1416 | Expression::ArrayIndex { array, .. } => array.try_set_rw(ctx, what, node), |
1417 | _ => { |
1418 | ctx.diag.push_error(format!(" {what} needs to be done on a property" ), node); |
1419 | false |
1420 | } |
1421 | } |
1422 | } |
1423 | |
1424 | /// Unwrap DebugHook expressions to their contained sub-expression |
1425 | pub fn ignore_debug_hooks(&self) -> &Expression { |
1426 | match self { |
1427 | Expression::DebugHook { expression, .. } => expression.as_ref(), |
1428 | _ => self, |
1429 | } |
1430 | } |
1431 | } |
1432 | |
1433 | fn model_inner_type(model: &Expression) -> Type { |
1434 | match model { |
1435 | Expression::Cast { from: &Box, to: Type::Model } => model_inner_type(model:from), |
1436 | Expression::CodeBlock(cb: &Vec) => cb.last().map_or(default:Type::Invalid, f:model_inner_type), |
1437 | _ => match model.ty() { |
1438 | Type::Float32 | Type::Int32 => Type::Int32, |
1439 | Type::Array(elem: Rc) => (*elem).clone(), |
1440 | _ => Type::Invalid, |
1441 | }, |
1442 | } |
1443 | } |
1444 | |
1445 | /// The expression in the Element::binding hash table |
1446 | #[derive (Debug, Clone, derive_more::Deref, derive_more::DerefMut)] |
1447 | pub struct BindingExpression { |
1448 | #[deref] |
1449 | #[deref_mut] |
1450 | pub expression: Expression, |
1451 | /// The location of this expression in the source code |
1452 | pub span: Option<SourceLocation>, |
1453 | /// How deep is this binding declared in the hierarchy. When two binding are conflicting |
1454 | /// for the same priority (because of two way binding), the lower priority wins. |
1455 | /// The priority starts at 1, and each level of inlining adds one to the priority. |
1456 | /// 0 means the expression was added by some passes and it is not explicit in the source code |
1457 | pub priority: i32, |
1458 | |
1459 | pub animation: Option<PropertyAnimation>, |
1460 | |
1461 | /// The analysis information. None before it is computed |
1462 | pub analysis: Option<BindingAnalysis>, |
1463 | |
1464 | /// The properties this expression is aliased with using two way bindings |
1465 | pub two_way_bindings: Vec<NamedReference>, |
1466 | } |
1467 | |
1468 | impl std::convert::From<Expression> for BindingExpression { |
1469 | fn from(expression: Expression) -> Self { |
1470 | Self { |
1471 | expression, |
1472 | span: None, |
1473 | priority: 0, |
1474 | animation: Default::default(), |
1475 | analysis: Default::default(), |
1476 | two_way_bindings: Default::default(), |
1477 | } |
1478 | } |
1479 | } |
1480 | |
1481 | impl BindingExpression { |
1482 | pub fn new_uncompiled(node: SyntaxNode) -> Self { |
1483 | Self { |
1484 | expression: Expression::Uncompiled(node.clone()), |
1485 | span: Some(node.to_source_location()), |
1486 | priority: 1, |
1487 | animation: Default::default(), |
1488 | analysis: Default::default(), |
1489 | two_way_bindings: Default::default(), |
1490 | } |
1491 | } |
1492 | pub fn new_with_span(expression: Expression, span: SourceLocation) -> Self { |
1493 | Self { |
1494 | expression, |
1495 | span: Some(span), |
1496 | priority: 0, |
1497 | animation: Default::default(), |
1498 | analysis: Default::default(), |
1499 | two_way_bindings: Default::default(), |
1500 | } |
1501 | } |
1502 | |
1503 | /// Create an expression binding that simply is a two way binding to the other |
1504 | pub fn new_two_way(other: NamedReference) -> Self { |
1505 | Self { |
1506 | expression: Expression::Invalid, |
1507 | span: None, |
1508 | priority: 0, |
1509 | animation: Default::default(), |
1510 | analysis: Default::default(), |
1511 | two_way_bindings: vec![other], |
1512 | } |
1513 | } |
1514 | |
1515 | /// Merge the other into this one. Normally, &self is kept intact (has priority) |
1516 | /// unless the expression is invalid, in which case the other one is taken. |
1517 | /// |
1518 | /// Also the animation is taken if the other don't have one, and the two ways binding |
1519 | /// are taken into account. |
1520 | /// |
1521 | /// Returns true if the other expression was taken |
1522 | pub fn merge_with(&mut self, other: &Self) -> bool { |
1523 | if self.animation.is_none() { |
1524 | self.animation.clone_from(&other.animation); |
1525 | } |
1526 | let has_binding = self.has_binding(); |
1527 | self.two_way_bindings.extend_from_slice(&other.two_way_bindings); |
1528 | if !has_binding { |
1529 | self.priority = other.priority; |
1530 | self.expression = other.expression.clone(); |
1531 | true |
1532 | } else { |
1533 | false |
1534 | } |
1535 | } |
1536 | |
1537 | /// returns false if there is no expression or two way binding |
1538 | pub fn has_binding(&self) -> bool { |
1539 | !matches!(self.expression, Expression::Invalid) || !self.two_way_bindings.is_empty() |
1540 | } |
1541 | } |
1542 | |
1543 | impl Spanned for BindingExpression { |
1544 | fn span(&self) -> crate::diagnostics::Span { |
1545 | self.span.as_ref().map(|x: &SourceLocation| x.span()).unwrap_or_default() |
1546 | } |
1547 | fn source_file(&self) -> Option<&crate::diagnostics::SourceFile> { |
1548 | self.span.as_ref().and_then(|x: &SourceLocation| x.source_file()) |
1549 | } |
1550 | } |
1551 | |
1552 | #[derive (Default, Debug, Clone)] |
1553 | pub struct BindingAnalysis { |
1554 | /// true if that binding is part of a binding loop that already has been reported. |
1555 | pub is_in_binding_loop: Cell<bool>, |
1556 | |
1557 | /// true if the binding is a constant value that can be set without creating a binding at runtime |
1558 | pub is_const: bool, |
1559 | |
1560 | /// true if this binding does not depends on the value of property that are set externally. |
1561 | /// When true, this binding cannot be part of a binding loop involving external components |
1562 | pub no_external_dependencies: bool, |
1563 | } |
1564 | |
1565 | #[derive (Debug, Clone)] |
1566 | pub enum Path { |
1567 | Elements(Vec<PathElement>), |
1568 | Events(Vec<Expression>, Vec<Expression>), |
1569 | Commands(Box<Expression>), // expr must evaluate to string |
1570 | } |
1571 | |
1572 | #[derive (Debug, Clone)] |
1573 | pub struct PathElement { |
1574 | pub element_type: Rc<BuiltinElement>, |
1575 | pub bindings: BindingsMap, |
1576 | } |
1577 | |
1578 | #[derive (Clone, Debug, Default)] |
1579 | pub enum EasingCurve { |
1580 | #[default] |
1581 | Linear, |
1582 | CubicBezier(f32, f32, f32, f32), |
1583 | EaseInElastic, |
1584 | EaseOutElastic, |
1585 | EaseInOutElastic, |
1586 | EaseInBounce, |
1587 | EaseOutBounce, |
1588 | EaseInOutBounce, |
1589 | // CubicBezierNonConst([Box<Expression>; 4]), |
1590 | // Custom(Box<dyn Fn(f32)->f32>), |
1591 | } |
1592 | |
1593 | // The compiler generates ResourceReference::AbsolutePath for all references like @image-url("foo.png") |
1594 | // and the resource lowering path may change this to EmbeddedData if configured. |
1595 | #[derive (Clone, Debug)] |
1596 | pub enum ImageReference { |
1597 | None, |
1598 | AbsolutePath(SmolStr), |
1599 | EmbeddedData { resource_id: usize, extension: String }, |
1600 | EmbeddedTexture { resource_id: usize }, |
1601 | } |
1602 | |
1603 | /// Print the expression as a .slint code (not necessarily valid .slint) |
1604 | pub fn pretty_print(f: &mut dyn std::fmt::Write, expression: &Expression) -> std::fmt::Result { |
1605 | match expression { |
1606 | Expression::Invalid => write!(f, "<invalid>" ), |
1607 | Expression::Uncompiled(u) => write!(f, " {u:?}" ), |
1608 | Expression::StringLiteral(s) => write!(f, " {s:?}" ), |
1609 | Expression::NumberLiteral(vl, unit) => write!(f, " {vl}{unit}" ), |
1610 | Expression::BoolLiteral(b) => write!(f, " {b:?}" ), |
1611 | Expression::PropertyReference(a) => write!(f, " {a:?}" ), |
1612 | Expression::ElementReference(a) => write!(f, " {a:?}" ), |
1613 | Expression::RepeaterIndexReference { element } => { |
1614 | crate::namedreference::pretty_print_element_ref(f, element) |
1615 | } |
1616 | Expression::RepeaterModelReference { element } => { |
1617 | crate::namedreference::pretty_print_element_ref(f, element)?; |
1618 | write!(f, ".@model" ) |
1619 | } |
1620 | Expression::FunctionParameterReference { index, ty: _ } => write!(f, "_arg_ {index}" ), |
1621 | Expression::StoreLocalVariable { name, value } => { |
1622 | write!(f, " {name} = " )?; |
1623 | pretty_print(f, value) |
1624 | } |
1625 | Expression::ReadLocalVariable { name, ty: _ } => write!(f, " {name}" ), |
1626 | Expression::StructFieldAccess { base, name } => { |
1627 | pretty_print(f, base)?; |
1628 | write!(f, ". {name}" ) |
1629 | } |
1630 | Expression::ArrayIndex { array, index } => { |
1631 | pretty_print(f, array)?; |
1632 | write!(f, "[" )?; |
1633 | pretty_print(f, index)?; |
1634 | write!(f, "]" ) |
1635 | } |
1636 | Expression::Cast { from, to } => { |
1637 | write!(f, "(" )?; |
1638 | pretty_print(f, from)?; |
1639 | write!(f, "/* as {to} */)" ) |
1640 | } |
1641 | Expression::CodeBlock(c) => { |
1642 | write!(f, " {{ " )?; |
1643 | for e in c { |
1644 | pretty_print(f, e)?; |
1645 | write!(f, "; " )?; |
1646 | } |
1647 | write!(f, " }}" ) |
1648 | } |
1649 | Expression::FunctionCall { function, arguments, source_location: _ } => { |
1650 | match function { |
1651 | Callable::Builtin(b) => write!(f, " {b:?}" )?, |
1652 | Callable::Callback(nr) | Callable::Function(nr) => write!(f, " {nr:?}" )?, |
1653 | } |
1654 | write!(f, "(" )?; |
1655 | for e in arguments { |
1656 | pretty_print(f, e)?; |
1657 | write!(f, ", " )?; |
1658 | } |
1659 | write!(f, ")" ) |
1660 | } |
1661 | Expression::SelfAssignment { lhs, rhs, op, .. } => { |
1662 | pretty_print(f, lhs)?; |
1663 | write!(f, " {}= " , if *op == '=' { ' ' } else { *op })?; |
1664 | pretty_print(f, rhs) |
1665 | } |
1666 | Expression::BinaryExpression { lhs, rhs, op } => { |
1667 | write!(f, "(" )?; |
1668 | pretty_print(f, lhs)?; |
1669 | match *op { |
1670 | '=' | '!' => write!(f, " {op}= " )?, |
1671 | _ => write!(f, " {op} " )?, |
1672 | }; |
1673 | pretty_print(f, rhs)?; |
1674 | write!(f, ")" ) |
1675 | } |
1676 | Expression::UnaryOp { sub, op } => { |
1677 | write!(f, " {op}" )?; |
1678 | pretty_print(f, sub) |
1679 | } |
1680 | Expression::ImageReference { resource_ref, .. } => write!(f, " {resource_ref:?}" ), |
1681 | Expression::Condition { condition, true_expr, false_expr } => { |
1682 | write!(f, "if (" )?; |
1683 | pretty_print(f, condition)?; |
1684 | write!(f, ") {{ " )?; |
1685 | pretty_print(f, true_expr)?; |
1686 | write!(f, " }} else {{ " )?; |
1687 | pretty_print(f, false_expr)?; |
1688 | write!(f, " }}" ) |
1689 | } |
1690 | Expression::Array { element_ty: _, values } => { |
1691 | write!(f, "[" )?; |
1692 | for e in values { |
1693 | pretty_print(f, e)?; |
1694 | write!(f, ", " )?; |
1695 | } |
1696 | write!(f, "]" ) |
1697 | } |
1698 | Expression::Struct { ty: _, values } => { |
1699 | write!(f, " {{ " )?; |
1700 | for (name, e) in values { |
1701 | write!(f, " {name}: " )?; |
1702 | pretty_print(f, e)?; |
1703 | write!(f, ", " )?; |
1704 | } |
1705 | write!(f, " }}" ) |
1706 | } |
1707 | Expression::PathData(data) => write!(f, " {data:?}" ), |
1708 | Expression::EasingCurve(e) => write!(f, " {e:?}" ), |
1709 | Expression::LinearGradient { angle, stops } => { |
1710 | write!(f, "@linear-gradient(" )?; |
1711 | pretty_print(f, angle)?; |
1712 | for (c, s) in stops { |
1713 | write!(f, ", " )?; |
1714 | pretty_print(f, c)?; |
1715 | write!(f, " " )?; |
1716 | pretty_print(f, s)?; |
1717 | } |
1718 | write!(f, ")" ) |
1719 | } |
1720 | Expression::RadialGradient { stops } => { |
1721 | write!(f, "@radial-gradient(circle" )?; |
1722 | for (c, s) in stops { |
1723 | write!(f, ", " )?; |
1724 | pretty_print(f, c)?; |
1725 | write!(f, " " )?; |
1726 | pretty_print(f, s)?; |
1727 | } |
1728 | write!(f, ")" ) |
1729 | } |
1730 | Expression::EnumerationValue(e) => match e.enumeration.values.get(e.value) { |
1731 | Some(val) => write!(f, " {}. {}" , e.enumeration.name, val), |
1732 | None => write!(f, " {}. {}" , e.enumeration.name, e.value), |
1733 | }, |
1734 | Expression::ReturnStatement(e) => { |
1735 | write!(f, "return " )?; |
1736 | e.as_ref().map(|e| pretty_print(f, e)).unwrap_or(Ok(())) |
1737 | } |
1738 | Expression::LayoutCacheAccess { layout_cache_prop, index, repeater_index } => { |
1739 | write!( |
1740 | f, |
1741 | " {:?}[ {}{}]" , |
1742 | layout_cache_prop, |
1743 | index, |
1744 | if repeater_index.is_some() { " + $index" } else { "" } |
1745 | ) |
1746 | } |
1747 | Expression::ComputeLayoutInfo(..) => write!(f, "layout_info(..)" ), |
1748 | Expression::SolveLayout(..) => write!(f, "solve_layout(..)" ), |
1749 | Expression::MinMax { ty: _, op, lhs, rhs } => { |
1750 | match op { |
1751 | MinMaxOp::Min => write!(f, "min(" )?, |
1752 | MinMaxOp::Max => write!(f, "max(" )?, |
1753 | } |
1754 | pretty_print(f, lhs)?; |
1755 | write!(f, ", " )?; |
1756 | pretty_print(f, rhs)?; |
1757 | write!(f, ")" ) |
1758 | } |
1759 | Expression::EmptyComponentFactory => write!(f, "<empty-component-factory>" ), |
1760 | Expression::DebugHook { expression, id } => { |
1761 | write!(f, "debug-hook(" )?; |
1762 | pretty_print(f, expression)?; |
1763 | write!(f, " \"{id}\")" ) |
1764 | } |
1765 | } |
1766 | } |
1767 | |