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