1use std::rc::Rc;
7
8use crate::diagnostics::{BuildDiagnostics, Spanned};
9use crate::expression_tree::{
10 BuiltinFunction, BuiltinMacroFunction, Callable, EasingCurve, Expression, Unit,
11};
12use crate::langtype::{ElementType, Enumeration, EnumerationValue, Type};
13use crate::namedreference::NamedReference;
14use crate::object_tree::{ElementRc, PropertyVisibility};
15use crate::parser::NodeOrToken;
16use crate::typeregister::TypeRegister;
17use smol_str::{SmolStr, ToSmolStr};
18use std::cell::RefCell;
19
20pub use i_slint_common::color_parsing::named_colors;
21
22pub struct LookupCtx<'a> {
24 pub property_name: Option<&'a str>,
26
27 pub property_type: Type,
30
31 pub component_scope: &'a [ElementRc],
33
34 pub diag: &'a mut BuildDiagnostics,
36
37 pub arguments: Vec<SmolStr>,
39
40 pub type_register: &'a TypeRegister,
42
43 pub type_loader: Option<&'a crate::typeloader::TypeLoader>,
46
47 pub current_token: Option<NodeOrToken>,
49
50 pub local_variables: Vec<Vec<(SmolStr, Type)>>,
52}
53
54impl<'a> LookupCtx<'a> {
55 pub fn empty_context(type_register: &'a TypeRegister, diag: &'a mut BuildDiagnostics) -> Self {
57 Self {
58 property_name: Default::default(),
59 property_type: Default::default(),
60 component_scope: Default::default(),
61 diag,
62 arguments: Default::default(),
63 type_register,
64 type_loader: None,
65 current_token: None,
66 local_variables: Default::default(),
67 }
68 }
69
70 pub fn return_type(&self) -> &Type {
71 match &self.property_type {
72 Type::Callback(f) | Type::Function(f) => &f.return_type,
73 _ => &self.property_type,
74 }
75 }
76
77 pub fn is_legacy_component(&self) -> bool {
78 self.component_scope.first().is_some_and(|e| e.borrow().is_legacy_syntax)
79 }
80
81 pub fn is_local_element(&self, elem: &ElementRc) -> bool {
83 Option::zip(
84 elem.borrow().enclosing_component.upgrade(),
85 self.component_scope.first().and_then(|x| x.borrow().enclosing_component.upgrade()),
86 )
87 .is_none_or(|(x, y)| Rc::ptr_eq(&x, &y))
88 }
89}
90
91#[derive(Debug)]
92pub enum LookupResult {
93 Expression {
94 expression: Expression,
95 deprecated: Option<String>,
97 },
98 Enumeration(Rc<Enumeration>),
99 Namespace(BuiltinNamespace),
100 Callable(LookupResultCallable),
101}
102
103#[derive(Debug)]
104pub enum LookupResultCallable {
105 Callable(Callable),
106 Macro(BuiltinMacroFunction),
107 MemberFunction {
109 base: Expression,
111 base_node: Option<NodeOrToken>,
112 member: Box<LookupResultCallable>,
113 },
114}
115
116#[derive(Debug, derive_more::Display)]
117pub enum BuiltinNamespace {
118 Colors,
119 Easing,
120 Math,
121 Key,
122 SlintInternal,
123}
124
125impl From<Expression> for LookupResult {
126 fn from(expression: Expression) -> Self {
127 Self::Expression { expression, deprecated: None }
128 }
129}
130impl From<Callable> for LookupResult {
131 fn from(callable: Callable) -> Self {
132 Self::Callable(LookupResultCallable::Callable(callable))
133 }
134}
135impl From<BuiltinMacroFunction> for LookupResult {
136 fn from(macro_function: BuiltinMacroFunction) -> Self {
137 Self::Callable(LookupResultCallable::Macro(macro_function))
138 }
139}
140impl From<BuiltinFunction> for LookupResult {
141 fn from(function: BuiltinFunction) -> Self {
142 Self::Callable(LookupResultCallable::Callable(Callable::Builtin(function)))
143 }
144}
145
146impl LookupResult {
147 pub fn deprecated(&self) -> Option<&str> {
148 match self {
149 Self::Expression { deprecated: Some(x), .. } => Some(x.as_str()),
150 _ => None,
151 }
152 }
153}
154
155pub trait LookupObject {
157 fn for_each_entry<R>(
160 &self,
161 ctx: &LookupCtx,
162 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
163 ) -> Option<R>;
164
165 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
168 self.for_each_entry(ctx, &mut |prop, expr| (prop == name).then_some(expr))
169 }
170}
171
172impl<T1: LookupObject, T2: LookupObject> LookupObject for (T1, T2) {
173 fn for_each_entry<R>(
174 &self,
175 ctx: &LookupCtx,
176 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
177 ) -> Option<R> {
178 self.0.for_each_entry(ctx, f).or_else(|| self.1.for_each_entry(ctx, f))
179 }
180
181 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
182 self.0.lookup(ctx, name).or_else(|| self.1.lookup(ctx, name))
183 }
184}
185
186impl LookupObject for LookupResult {
187 fn for_each_entry<R>(
188 &self,
189 ctx: &LookupCtx,
190 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
191 ) -> Option<R> {
192 match self {
193 LookupResult::Expression { expression, .. } => expression.for_each_entry(ctx, f),
194 LookupResult::Enumeration(e) => e.for_each_entry(ctx, f),
195 LookupResult::Namespace(BuiltinNamespace::Colors) => {
196 (ColorSpecific, ColorFunctions).for_each_entry(ctx, f)
197 }
198 LookupResult::Namespace(BuiltinNamespace::Easing) => {
199 EasingSpecific.for_each_entry(ctx, f)
200 }
201 LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.for_each_entry(ctx, f),
202 LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.for_each_entry(ctx, f),
203 LookupResult::Namespace(BuiltinNamespace::SlintInternal) => {
204 SlintInternal.for_each_entry(ctx, f)
205 }
206 LookupResult::Callable(..) => None,
207 }
208 }
209
210 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
211 match self {
212 LookupResult::Expression { expression, .. } => expression.lookup(ctx, name),
213 LookupResult::Enumeration(e) => e.lookup(ctx, name),
214 LookupResult::Namespace(BuiltinNamespace::Colors) => {
215 (ColorSpecific, ColorFunctions).lookup(ctx, name)
216 }
217 LookupResult::Namespace(BuiltinNamespace::Easing) => EasingSpecific.lookup(ctx, name),
218 LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.lookup(ctx, name),
219 LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.lookup(ctx, name),
220 LookupResult::Namespace(BuiltinNamespace::SlintInternal) => {
221 SlintInternal.lookup(ctx, name)
222 }
223 LookupResult::Callable(..) => None,
224 }
225 }
226}
227
228struct LocalVariableLookup;
229impl LookupObject for LocalVariableLookup {
230 fn for_each_entry<R>(
231 &self,
232 ctx: &LookupCtx,
233 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
234 ) -> Option<R> {
235 for scope in ctx.local_variables.iter() {
236 for (name, ty) in scope {
237 if let Some(r) = f(
238 &name.strip_prefix("local_").unwrap_or(name).into(),
240 Expression::ReadLocalVariable { name: name.clone(), ty: ty.clone() }.into(),
241 ) {
242 return Some(r);
243 }
244 }
245 }
246 None
247 }
248}
249
250struct ArgumentsLookup;
251impl LookupObject for ArgumentsLookup {
252 fn for_each_entry<R>(
253 &self,
254 ctx: &LookupCtx,
255 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
256 ) -> Option<R> {
257 let args = match &ctx.property_type {
258 Type::Callback(f) | Type::Function(f) => &f.args,
259 _ => return None,
260 };
261 for (index, (name, ty)) in ctx.arguments.iter().zip(args.iter()).enumerate() {
262 if let Some(r) =
263 f(name, Expression::FunctionParameterReference { index, ty: ty.clone() }.into())
264 {
265 return Some(r);
266 }
267 }
268 None
269 }
270}
271
272struct SpecialIdLookup;
273impl LookupObject for SpecialIdLookup {
274 fn for_each_entry<R>(
275 &self,
276 ctx: &LookupCtx,
277 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
278 ) -> Option<R> {
279 let last = ctx.component_scope.last();
280 let mut f = |n, e: Expression| f(&SmolStr::new_static(n), e.into());
281 None.or_else(|| f("self", Expression::ElementReference(Rc::downgrade(last?))))
282 .or_else(|| {
283 let len = ctx.component_scope.len();
284 if len >= 2 {
285 f(
286 "parent",
287 Expression::ElementReference(Rc::downgrade(&ctx.component_scope[len - 2])),
288 )
289 } else {
290 None
291 }
292 })
293 .or_else(|| f("true", Expression::BoolLiteral(true)))
294 .or_else(|| f("false", Expression::BoolLiteral(false)))
295 }
297}
298
299struct IdLookup;
300impl LookupObject for IdLookup {
301 fn for_each_entry<R>(
302 &self,
303 ctx: &LookupCtx,
304 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
305 ) -> Option<R> {
306 fn visit<R>(
307 root: &ElementRc,
308 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
309 ) -> Option<R> {
310 if !root.borrow().id.is_empty()
311 && let Some(r) =
312 f(&root.borrow().id, Expression::ElementReference(Rc::downgrade(root)).into())
313 {
314 return Some(r);
315 }
316 for x in &root.borrow().children {
317 if x.borrow().repeated.is_some() {
318 continue;
319 }
320 if let Some(r) = visit(x, f) {
321 return Some(r);
322 }
323 }
324 None
325 }
326 for e in ctx.component_scope.iter().rev() {
327 if e.borrow().repeated.is_some()
328 && let Some(r) = visit(e, f)
329 {
330 return Some(r);
331 }
332 }
333 if let Some(root) = ctx.component_scope.first()
334 && let Some(r) = visit(root, f)
335 {
336 return Some(r);
337 }
338 None
339 }
340 }
342
343pub struct InScopeLookup;
345impl InScopeLookup {
346 fn visit_scope<R>(
347 ctx: &LookupCtx,
348 mut visit_entry: impl FnMut(&SmolStr, LookupResult) -> Option<R>,
349 mut visit_legacy_scope: impl FnMut(&ElementRc) -> Option<R>,
350 mut visit_scope: impl FnMut(&ElementRc) -> Option<R>,
351 ) -> Option<R> {
352 let is_legacy = ctx.is_legacy_component();
353 for (idx, elem) in ctx.component_scope.iter().rev().enumerate() {
354 if let Some(repeated) = &elem.borrow().repeated {
355 if !repeated.index_id.is_empty()
356 && let Some(r) = visit_entry(
357 &repeated.index_id,
358 Expression::RepeaterIndexReference { element: Rc::downgrade(elem) }.into(),
359 )
360 {
361 return Some(r);
362 }
363 if !repeated.model_data_id.is_empty()
364 && let Some(r) = visit_entry(
365 &repeated.model_data_id,
366 Expression::RepeaterModelReference { element: Rc::downgrade(elem) }.into(),
367 )
368 {
369 return Some(r);
370 }
371 }
372
373 if is_legacy {
374 if (elem.borrow().repeated.is_some()
375 || idx == 0
376 || idx == ctx.component_scope.len() - 1)
377 && let Some(r) = visit_legacy_scope(elem)
378 {
379 return Some(r);
380 }
381 } else if let Some(r) = visit_scope(elem) {
382 return Some(r);
383 }
384 }
385 None
386 }
387}
388impl LookupObject for InScopeLookup {
389 fn for_each_entry<R>(
390 &self,
391 ctx: &LookupCtx,
392 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
393 ) -> Option<R> {
394 let f = RefCell::new(f);
395 Self::visit_scope(
396 ctx,
397 |str, r| f.borrow_mut()(str, r),
398 |elem| elem.for_each_entry(ctx, *f.borrow_mut()),
399 |elem| {
400 for (name, prop) in &elem.borrow().property_declarations {
401 let e = expression_from_reference(
402 NamedReference::new(elem, name.clone()),
403 &prop.property_type,
404 None,
405 );
406 if let Some(r) = f.borrow_mut()(name, e) {
407 return Some(r);
408 }
409 }
410 None
411 },
412 )
413 }
414
415 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
416 if name.is_empty() {
417 return None;
418 }
419 Self::visit_scope(
420 ctx,
421 |str, r| (str == name).then_some(r),
422 |elem| elem.lookup(ctx, name),
423 |elem| {
424 elem.borrow().property_declarations.get(name).map(|prop| {
425 expression_from_reference(
426 NamedReference::new(elem, name.clone()),
427 &prop.property_type,
428 None,
429 )
430 })
431 },
432 )
433 }
434}
435
436impl LookupObject for ElementRc {
437 fn for_each_entry<R>(
438 &self,
439 ctx: &LookupCtx,
440 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
441 ) -> Option<R> {
442 for (name, prop) in &self.borrow().property_declarations {
443 let r = expression_from_reference(
444 NamedReference::new(self, name.clone()),
445 &prop.property_type,
446 check_extra_deprecated(self, ctx, name),
447 );
448 if let Some(r) = f(name, r) {
449 return Some(r);
450 }
451 }
452 let list = self.borrow().base_type.property_list();
453 for (name, ty) in list {
454 let e = expression_from_reference(NamedReference::new(self, name.clone()), &ty, None);
455 if let Some(r) = f(&name, e) {
456 return Some(r);
457 }
458 }
459 if !(matches!(self.borrow().base_type, ElementType::Global)) {
460 for (name, ty, _) in crate::typeregister::reserved_properties() {
461 let name = SmolStr::new_static(name);
462 let e =
463 expression_from_reference(NamedReference::new(self, name.clone()), &ty, None);
464 if let Some(r) = f(&name, e) {
465 return Some(r);
466 }
467 }
468 }
469 None
470 }
471
472 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
473 let lookup_result = self.borrow().lookup_property(name);
474 if lookup_result.property_type != Type::Invalid
475 && (lookup_result.is_local_to_component
476 || lookup_result.property_visibility != PropertyVisibility::Private)
477 {
478 let deprecated = (lookup_result.resolved_name != name.as_str())
479 .then(|| lookup_result.resolved_name.to_string())
480 .or_else(|| check_extra_deprecated(self, ctx, name));
481 Some(expression_from_reference(
482 NamedReference::new(self, lookup_result.resolved_name.to_smolstr()),
483 &lookup_result.property_type,
484 deprecated,
485 ))
486 } else {
487 None
488 }
489 }
490}
491
492pub fn check_extra_deprecated(
493 elem: &ElementRc,
494 ctx: &LookupCtx<'_>,
495 name: &SmolStr,
496) -> Option<String> {
497 if crate::typeregister::DEPRECATED_ROTATION_ORIGIN_PROPERTIES.iter().any(|(p, _)| p == name) {
498 return Some(format!("transform-origin.{}", &name[name.len() - 1..]));
499 }
500 let borrow = elem.borrow();
501 (!ctx.type_register.expose_internal_types
502 && matches!(
503 borrow.enclosing_component.upgrade().unwrap().id.as_str(),
504 "StyleMetrics" | "NativeStyleMetrics"
505 )
506 && borrow
507 .debug
508 .first()
509 .and_then(|x| x.node.source_file())
510 .is_none_or(|x| x.path().starts_with("builtin:"))
511 && !name.starts_with("layout-"))
512 .then(|| format!("Palette.{name}"))
513}
514
515fn expression_from_reference(
516 n: NamedReference,
517 ty: &Type,
518 deprecated: Option<String>,
519) -> LookupResult {
520 match ty {
521 Type::Callback { .. } => Callable::Callback(n).into(),
522 Type::InferredCallback => Callable::Callback(n).into(),
523 Type::Function(function) => {
524 let base_expr = Rc::downgrade(&n.element());
525 let callable = Callable::Function(n);
526 if matches!(function.args.first(), Some(Type::ElementReference)) {
529 LookupResult::Callable(LookupResultCallable::MemberFunction {
530 base: Expression::ElementReference(base_expr),
531 base_node: None,
532 member: Box::new(LookupResultCallable::Callable(callable)),
533 })
534 } else {
535 callable.into()
536 }
537 }
538 _ => LookupResult::Expression { expression: Expression::PropertyReference(n), deprecated },
539 }
540}
541
542struct LookupType;
544impl LookupObject for LookupType {
545 fn for_each_entry<R>(
546 &self,
547 ctx: &LookupCtx,
548 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
549 ) -> Option<R> {
550 for (name, ty) in ctx.type_register.all_types() {
551 if let Some(r) = Self::from_type(ty).and_then(|e| f(&name, e)) {
552 return Some(r);
553 }
554 }
555 for (name, ty) in ctx.type_register.all_elements() {
556 if let Some(r) = Self::from_element(ty, ctx, &name).and_then(|e| f(&name, e)) {
557 return Some(r);
558 }
559 }
560 None
561 }
562
563 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
564 Self::from_type(ctx.type_register.lookup(name))
565 .or_else(|| Self::from_element(ctx.type_register.lookup_element(name).ok()?, ctx, name))
566 }
567}
568impl LookupType {
569 fn from_type(ty: Type) -> Option<LookupResult> {
570 match ty {
571 Type::Enumeration(e) => Some(LookupResult::Enumeration(e)),
572 _ => None,
573 }
574 }
575
576 fn from_element(el: ElementType, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
577 match el {
578 ElementType::Component(c) if c.is_global() => {
579 if c.root_element
581 .borrow()
582 .builtin_type()
583 .is_some_and(|x| x.is_internal && x.name == name)
584 && !ctx.type_register.expose_internal_types
585 {
586 None
587 } else {
588 Some(Expression::ElementReference(Rc::downgrade(&c.root_element)).into())
589 }
590 }
591 _ => None,
592 }
593 }
594}
595
596pub struct ReturnTypeSpecificLookup;
598impl LookupObject for ReturnTypeSpecificLookup {
599 fn for_each_entry<R>(
600 &self,
601 ctx: &LookupCtx,
602 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
603 ) -> Option<R> {
604 match ctx.return_type() {
605 Type::Color => ColorSpecific.for_each_entry(ctx, f),
606 Type::Brush => ColorSpecific.for_each_entry(ctx, f),
607 Type::Easing => EasingSpecific.for_each_entry(ctx, f),
608 Type::Enumeration(enumeration) => enumeration.clone().for_each_entry(ctx, f),
609 _ => None,
610 }
611 }
612
613 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
614 match ctx.return_type() {
615 Type::Color => ColorSpecific.lookup(ctx, name),
616 Type::Brush => ColorSpecific.lookup(ctx, name),
617 Type::Easing => EasingSpecific.lookup(ctx, name),
618 Type::Enumeration(enumeration) => enumeration.clone().lookup(ctx, name),
619 _ => None,
620 }
621 }
622}
623
624struct ColorSpecific;
625impl LookupObject for ColorSpecific {
626 fn for_each_entry<R>(
627 &self,
628 _ctx: &LookupCtx,
629 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
630 ) -> Option<R> {
631 for (name, c) in named_colors().iter() {
632 if let Some(r) = f(&SmolStr::new_static(name), Self::as_result(*c)) {
633 return Some(r);
634 }
635 }
636 None
637 }
638 fn lookup(&self, _ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
639 named_colors().get(name.as_str()).map(|c| Self::as_result(*c))
640 }
641}
642impl ColorSpecific {
643 fn as_result(value: u32) -> LookupResult {
644 Expression::Cast {
645 from: Box::new(Expression::NumberLiteral(value as f64, Unit::None)),
646 to: Type::Color,
647 }
648 .into()
649 }
650}
651
652struct KeysLookup;
653
654macro_rules! special_keys_lookup {
655 ($($char:literal # $name:ident # $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|*;)*) => {
656 impl LookupObject for KeysLookup {
657 fn for_each_entry<R>(
658 &self,
659 _ctx: &LookupCtx,
660 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
661 ) -> Option<R> {
662 None
663 $(.or_else(|| {
664 let mut tmp = [0; 4];
665 f(&SmolStr::new_static(stringify!($name)), Expression::StringLiteral(SmolStr::new_inline($char.encode_utf8(&mut tmp))).into())
666 }))*
667 }
668 }
669 };
670}
671
672i_slint_common::for_each_special_keys!(special_keys_lookup);
673
674struct EasingSpecific;
675impl LookupObject for EasingSpecific {
676 fn for_each_entry<R>(
677 &self,
678 _ctx: &LookupCtx,
679 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
680 ) -> Option<R> {
681 use EasingCurve::CubicBezier;
682 let mut curve = |n, e| f(&SmolStr::new_static(n), Expression::EasingCurve(e).into());
683 let r = None
684 .or_else(|| curve("linear", EasingCurve::Linear))
685 .or_else(|| curve("ease-in-quad", CubicBezier(0.11, 0.0, 0.5, 0.0)))
686 .or_else(|| curve("ease-out-quad", CubicBezier(0.5, 1.0, 0.89, 1.0)))
687 .or_else(|| curve("ease-in-out-quad", CubicBezier(0.45, 0.0, 0.55, 1.0)))
688 .or_else(|| curve("ease", CubicBezier(0.25, 0.1, 0.25, 1.0)))
689 .or_else(|| curve("ease-in", CubicBezier(0.42, 0.0, 1.0, 1.0)))
690 .or_else(|| curve("ease-in-out", CubicBezier(0.42, 0.0, 0.58, 1.0)))
691 .or_else(|| curve("ease-out", CubicBezier(0.0, 0.0, 0.58, 1.0)))
692 .or_else(|| curve("ease-in-quart", CubicBezier(0.5, 0.0, 0.75, 0.0)))
693 .or_else(|| curve("ease-out-quart", CubicBezier(0.25, 1.0, 0.5, 1.0)))
694 .or_else(|| curve("ease-in-out-quart", CubicBezier(0.76, 0.0, 0.24, 1.0)))
695 .or_else(|| curve("ease-in-quint", CubicBezier(0.64, 0.0, 0.78, 0.0)))
696 .or_else(|| curve("ease-out-quint", CubicBezier(0.22, 1.0, 0.36, 1.0)))
697 .or_else(|| curve("ease-in-out-quint", CubicBezier(0.83, 0.0, 0.17, 1.0)))
698 .or_else(|| curve("ease-in-expo", CubicBezier(0.7, 0.0, 0.84, 0.0)))
699 .or_else(|| curve("ease-out-expo", CubicBezier(0.16, 1.0, 0.3, 1.0)))
700 .or_else(|| curve("ease-in-out-expo", CubicBezier(0.87, 0.0, 0.13, 1.0)))
701 .or_else(|| curve("ease-in-back", CubicBezier(0.36, 0.0, 0.66, -0.56)))
702 .or_else(|| curve("ease-out-back", CubicBezier(0.34, 1.56, 0.64, 1.0)))
703 .or_else(|| curve("ease-in-out-back", CubicBezier(0.68, -0.6, 0.32, 1.6)))
704 .or_else(|| curve("ease-in-sine", CubicBezier(0.12, 0.0, 0.39, 0.0)))
705 .or_else(|| curve("ease-out-sine", CubicBezier(0.61, 1.0, 0.88, 1.0)))
706 .or_else(|| curve("ease-in-out-sine", CubicBezier(0.37, 0.0, 0.63, 1.0)))
707 .or_else(|| curve("ease-in-circ", CubicBezier(0.55, 0.0, 1.0, 0.45)))
708 .or_else(|| curve("ease-out-circ", CubicBezier(0.0, 0.55, 0.45, 1.0)))
709 .or_else(|| curve("ease-in-out-circ", CubicBezier(0.85, 0.0, 0.15, 1.0)))
710 .or_else(|| curve("ease-in-elastic", EasingCurve::EaseInElastic))
711 .or_else(|| curve("ease-out-elastic", EasingCurve::EaseOutElastic))
712 .or_else(|| curve("ease-in-out-elastic", EasingCurve::EaseInOutElastic))
713 .or_else(|| curve("ease-in-bounce", EasingCurve::EaseInBounce))
714 .or_else(|| curve("ease-out-bounce", EasingCurve::EaseOutBounce))
715 .or_else(|| curve("ease-in-out-bounce", EasingCurve::EaseInOutBounce));
716 r.or_else(|| {
717 f(&SmolStr::new_static("cubic-bezier"), BuiltinMacroFunction::CubicBezier.into())
718 })
719 }
720}
721
722impl LookupObject for Rc<Enumeration> {
723 fn for_each_entry<R>(
724 &self,
725 _ctx: &LookupCtx,
726 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
727 ) -> Option<R> {
728 for (value, name) in self.values.iter().enumerate() {
729 if let Some(r) = f(
730 name,
731 Expression::EnumerationValue(EnumerationValue { value, enumeration: self.clone() })
732 .into(),
733 ) {
734 return Some(r);
735 }
736 }
737 None
738 }
739}
740
741struct MathFunctions;
742impl LookupObject for MathFunctions {
743 fn for_each_entry<R>(
744 &self,
745 _ctx: &LookupCtx,
746 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
747 ) -> Option<R> {
748 let mut f = |n, e| f(&SmolStr::new_static(n), e);
749 let b = |b| LookupResult::from(Callable::Builtin(b));
750 None.or_else(|| f("mod", BuiltinMacroFunction::Mod.into()))
751 .or_else(|| f("round", b(BuiltinFunction::Round)))
752 .or_else(|| f("ceil", b(BuiltinFunction::Ceil)))
753 .or_else(|| f("floor", b(BuiltinFunction::Floor)))
754 .or_else(|| f("clamp", BuiltinMacroFunction::Clamp.into()))
755 .or_else(|| f("abs", BuiltinMacroFunction::Abs.into()))
756 .or_else(|| f("sqrt", b(BuiltinFunction::Sqrt)))
757 .or_else(|| f("max", BuiltinMacroFunction::Max.into()))
758 .or_else(|| f("min", BuiltinMacroFunction::Min.into()))
759 .or_else(|| f("sin", b(BuiltinFunction::Sin)))
760 .or_else(|| f("cos", b(BuiltinFunction::Cos)))
761 .or_else(|| f("tan", b(BuiltinFunction::Tan)))
762 .or_else(|| f("asin", b(BuiltinFunction::ASin)))
763 .or_else(|| f("acos", b(BuiltinFunction::ACos)))
764 .or_else(|| f("atan", b(BuiltinFunction::ATan)))
765 .or_else(|| f("atan2", b(BuiltinFunction::ATan2)))
766 .or_else(|| f("log", b(BuiltinFunction::Log)))
767 .or_else(|| f("ln", b(BuiltinFunction::Ln)))
768 .or_else(|| f("pow", b(BuiltinFunction::Pow)))
769 .or_else(|| f("exp", b(BuiltinFunction::Exp)))
770 .or_else(|| f("sign", BuiltinMacroFunction::Sign.into()))
771 }
772}
773
774struct SlintInternal;
775impl LookupObject for SlintInternal {
776 fn for_each_entry<R>(
777 &self,
778 ctx: &LookupCtx,
779 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
780 ) -> Option<R> {
781 let sl = || ctx.current_token.as_ref().map(|t| t.to_source_location());
782 let mut f = |n, e: LookupResult| f(&SmolStr::new_static(n), e);
783 let b = |b| LookupResult::from(Callable::Builtin(b));
784 None.or_else(|| {
785 let style = ctx.type_loader.and_then(|tl| tl.compiler_config.style.as_ref());
786 f(
787 "color-scheme",
788 if style.is_some_and(|s| s.ends_with("-light")) {
789 let e = crate::typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone());
790 Expression::EnumerationValue(e.try_value_from_string("light").unwrap())
791 } else if style.is_some_and(|s| s.ends_with("-dark")) {
792 let e = crate::typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone());
793 Expression::EnumerationValue(e.try_value_from_string("dark").unwrap())
794 } else {
795 Expression::FunctionCall {
796 function: BuiltinFunction::ColorScheme.into(),
797 arguments: Vec::new(),
798 source_location: sl(),
799 }
800 }
801 .into(),
802 )
803 })
804 .or_else(|| {
805 f(
806 "use-24-hour-format",
807 Expression::FunctionCall {
808 function: BuiltinFunction::Use24HourFormat.into(),
809 arguments: Vec::new(),
810 source_location: sl(),
811 }
812 .into(),
813 )
814 })
815 .or_else(|| f("month-day-count", b(BuiltinFunction::MonthDayCount)))
816 .or_else(|| f("month-offset", b(BuiltinFunction::MonthOffset)))
817 .or_else(|| f("format-date", b(BuiltinFunction::FormatDate)))
818 .or_else(|| f("date-now", b(BuiltinFunction::DateNow)))
819 .or_else(|| f("valid-date", b(BuiltinFunction::ValidDate)))
820 .or_else(|| f("parse-date", b(BuiltinFunction::ParseDate)))
821 }
822}
823
824struct ColorFunctions;
825impl LookupObject for ColorFunctions {
826 fn for_each_entry<R>(
827 &self,
828 _ctx: &LookupCtx,
829 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
830 ) -> Option<R> {
831 let mut f = |n, m| f(&SmolStr::new_static(n), LookupResult::from(m));
832 None.or_else(|| f("rgb", BuiltinMacroFunction::Rgb))
833 .or_else(|| f("rgba", BuiltinMacroFunction::Rgb))
834 .or_else(|| f("hsv", BuiltinMacroFunction::Hsv))
835 .or_else(|| f("oklch", BuiltinMacroFunction::Oklch))
836 }
837}
838
839struct BuiltinFunctionLookup;
840impl LookupObject for BuiltinFunctionLookup {
841 fn for_each_entry<R>(
842 &self,
843 ctx: &LookupCtx,
844 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
845 ) -> Option<R> {
846 (MathFunctions, ColorFunctions)
847 .for_each_entry(ctx, f)
848 .or_else(|| f(&SmolStr::new_static("debug"), BuiltinMacroFunction::Debug.into()))
849 .or_else(|| {
850 f(&SmolStr::new_static("animation-tick"), BuiltinFunction::AnimationTick.into())
851 })
852 }
853}
854
855struct BuiltinNamespaceLookup;
856impl LookupObject for BuiltinNamespaceLookup {
857 fn for_each_entry<R>(
858 &self,
859 ctx: &LookupCtx,
860 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
861 ) -> Option<R> {
862 let mut f = |s, res| f(&SmolStr::new_static(s), res);
863 None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors)))
864 .or_else(|| f("Easing", LookupResult::Namespace(BuiltinNamespace::Easing)))
865 .or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math)))
866 .or_else(|| f("Key", LookupResult::Namespace(BuiltinNamespace::Key)))
867 .or_else(|| {
868 if ctx.type_register.expose_internal_types {
869 f("SlintInternal", LookupResult::Namespace(BuiltinNamespace::SlintInternal))
870 } else {
871 None
872 }
873 })
874 }
875}
876
877pub fn global_lookup() -> impl LookupObject {
878 (
879 LocalVariableLookup,
880 (
881 ArgumentsLookup,
882 (
883 SpecialIdLookup,
884 (
885 IdLookup,
886 (
887 InScopeLookup,
888 (
889 LookupType,
890 (
891 BuiltinNamespaceLookup,
892 (ReturnTypeSpecificLookup, BuiltinFunctionLookup),
893 ),
894 ),
895 ),
896 ),
897 ),
898 ),
899 )
900}
901
902impl LookupObject for Expression {
903 fn for_each_entry<R>(
904 &self,
905 ctx: &LookupCtx,
906 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
907 ) -> Option<R> {
908 match self {
909 Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f),
910 _ => match self.ty() {
911 Type::Struct(s) => {
912 for name in s.fields.keys() {
913 if let Some(r) = f(
914 name,
915 Expression::StructFieldAccess {
916 base: Box::new(self.clone()),
917 name: name.clone(),
918 }
919 .into(),
920 ) {
921 return Some(r);
922 }
923 }
924 None
925 }
926 Type::String => StringExpression(self).for_each_entry(ctx, f),
927 Type::Brush | Type::Color => ColorExpression(self).for_each_entry(ctx, f),
928 Type::Image => ImageExpression(self).for_each_entry(ctx, f),
929 Type::Array(_) => ArrayExpression(self).for_each_entry(ctx, f),
930 Type::Float32 | Type::Int32 | Type::Percent => {
931 NumberExpression(self).for_each_entry(ctx, f)
932 }
933 ty if ty.as_unit_product().is_some() => {
934 NumberWithUnitExpression(self).for_each_entry(ctx, f)
935 }
936 _ => None,
937 },
938 }
939 }
940
941 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
942 match self {
943 Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name),
944 _ => match self.ty() {
945 Type::Struct(s) => s.fields.contains_key(name).then(|| {
946 LookupResult::from(Expression::StructFieldAccess {
947 base: Box::new(self.clone()),
948 name: name.clone(),
949 })
950 }),
951 Type::String => StringExpression(self).lookup(ctx, name),
952 Type::Brush | Type::Color => ColorExpression(self).lookup(ctx, name),
953 Type::Image => ImageExpression(self).lookup(ctx, name),
954 Type::Array(_) => ArrayExpression(self).lookup(ctx, name),
955 Type::Float32 | Type::Int32 | Type::Percent => {
956 NumberExpression(self).lookup(ctx, name)
957 }
958 ty if ty.as_unit_product().is_some() => {
959 NumberWithUnitExpression(self).lookup(ctx, name)
960 }
961 _ => None,
962 },
963 }
964 }
965}
966
967struct StringExpression<'a>(&'a Expression);
968impl LookupObject for StringExpression<'_> {
969 fn for_each_entry<R>(
970 &self,
971 ctx: &LookupCtx,
972 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
973 ) -> Option<R> {
974 let member_function = |f: BuiltinFunction| {
975 LookupResult::Callable(LookupResultCallable::MemberFunction {
976 base: self.0.clone(),
977 base_node: ctx.current_token.clone(), member: LookupResultCallable::Callable(Callable::Builtin(f)).into(),
979 })
980 };
981 let function_call = |f: BuiltinFunction| {
982 LookupResult::from(Expression::FunctionCall {
983 function: Callable::Builtin(f),
984 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
985 arguments: vec![self.0.clone()],
986 })
987 };
988
989 let mut f = |s, res| f(&SmolStr::new_static(s), res);
990 None.or_else(|| f("is-float", member_function(BuiltinFunction::StringIsFloat)))
991 .or_else(|| f("to-float", member_function(BuiltinFunction::StringToFloat)))
992 .or_else(|| f("is-empty", function_call(BuiltinFunction::StringIsEmpty)))
993 .or_else(|| f("character-count", function_call(BuiltinFunction::StringCharacterCount)))
994 .or_else(|| f("to-lowercase", member_function(BuiltinFunction::StringToLowercase)))
995 .or_else(|| f("to-uppercase", member_function(BuiltinFunction::StringToUppercase)))
996 }
997}
998struct ColorExpression<'a>(&'a Expression);
999impl LookupObject for ColorExpression<'_> {
1000 fn for_each_entry<R>(
1001 &self,
1002 ctx: &LookupCtx,
1003 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1004 ) -> Option<R> {
1005 let member_function = |f: BuiltinFunction| {
1006 let base = if (f == BuiltinFunction::ColorHsvaStruct
1007 || f == BuiltinFunction::ColorOklchStruct)
1008 && self.0.ty() == Type::Brush
1009 {
1010 Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
1011 } else {
1012 self.0.clone()
1013 };
1014 LookupResult::Callable(LookupResultCallable::MemberFunction {
1015 base,
1016 base_node: ctx.current_token.clone(), member: Box::new(LookupResultCallable::Callable(Callable::Builtin(f))),
1018 })
1019 };
1020 let field_access = |f: &'static str| {
1021 let base = if self.0.ty() == Type::Brush {
1022 Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
1023 } else {
1024 self.0.clone()
1025 };
1026 LookupResult::from(Expression::StructFieldAccess {
1027 base: Box::new(Expression::FunctionCall {
1028 function: BuiltinFunction::ColorRgbaStruct.into(),
1029 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1030 arguments: vec![base],
1031 }),
1032 name: SmolStr::new_static(f),
1033 })
1034 };
1035
1036 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1037 None.or_else(|| f("red", field_access("red")))
1038 .or_else(|| f("green", field_access("green")))
1039 .or_else(|| f("blue", field_access("blue")))
1040 .or_else(|| f("alpha", field_access("alpha")))
1041 .or_else(|| f("to-hsv", member_function(BuiltinFunction::ColorHsvaStruct)))
1042 .or_else(|| f("to-oklch", member_function(BuiltinFunction::ColorOklchStruct)))
1043 .or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
1044 .or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
1045 .or_else(|| f("transparentize", member_function(BuiltinFunction::ColorTransparentize)))
1046 .or_else(|| f("with-alpha", member_function(BuiltinFunction::ColorWithAlpha)))
1047 .or_else(|| f("mix", member_function(BuiltinFunction::ColorMix)))
1048 }
1049}
1050
1051struct ImageExpression<'a>(&'a Expression);
1052impl LookupObject for ImageExpression<'_> {
1053 fn for_each_entry<R>(
1054 &self,
1055 ctx: &LookupCtx,
1056 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1057 ) -> Option<R> {
1058 let field_access = |f: &str| {
1059 LookupResult::from(Expression::StructFieldAccess {
1060 base: Box::new(Expression::FunctionCall {
1061 function: BuiltinFunction::ImageSize.into(),
1062 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1063 arguments: vec![self.0.clone()],
1064 }),
1065 name: f.into(),
1066 })
1067 };
1068 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1069 None.or_else(|| f("width", field_access("width")))
1070 .or_else(|| f("height", field_access("height")))
1071 }
1072}
1073
1074struct ArrayExpression<'a>(&'a Expression);
1075impl LookupObject for ArrayExpression<'_> {
1076 fn for_each_entry<R>(
1077 &self,
1078 ctx: &LookupCtx,
1079 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1080 ) -> Option<R> {
1081 let member_function = |f: BuiltinFunction| {
1082 LookupResult::from(Expression::FunctionCall {
1083 function: Callable::Builtin(f),
1084 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1085 arguments: vec![self.0.clone()],
1086 })
1087 };
1088 None.or_else(|| {
1089 f(&SmolStr::new_static("length"), member_function(BuiltinFunction::ArrayLength))
1090 })
1091 }
1092}
1093
1094struct NumberExpression<'a>(&'a Expression);
1096impl LookupObject for NumberExpression<'_> {
1097 fn for_each_entry<R>(
1098 &self,
1099 ctx: &LookupCtx,
1100 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1101 ) -> Option<R> {
1102 let member = |f: LookupResultCallable| {
1103 LookupResult::Callable(LookupResultCallable::MemberFunction {
1104 base: self.0.clone(),
1105 base_node: ctx.current_token.clone(), member: f.into(),
1107 })
1108 };
1109 let member_function = |f| member(LookupResultCallable::Callable(Callable::Builtin(f)));
1110 let member_macro = |f| member(LookupResultCallable::Macro(f));
1111
1112 let mut f2 = |s, res| f(&SmolStr::new_static(s), res);
1113 None.or_else(|| f2("round", member_function(BuiltinFunction::Round)))
1114 .or_else(|| f2("ceil", member_function(BuiltinFunction::Ceil)))
1115 .or_else(|| f2("floor", member_function(BuiltinFunction::Floor)))
1116 .or_else(|| f2("sqrt", member_function(BuiltinFunction::Sqrt)))
1117 .or_else(|| f2("asin", member_function(BuiltinFunction::ASin)))
1118 .or_else(|| f2("acos", member_function(BuiltinFunction::ACos)))
1119 .or_else(|| f2("atan", member_function(BuiltinFunction::ATan)))
1120 .or_else(|| f2("log", member_function(BuiltinFunction::Log)))
1121 .or_else(|| f2("ln", member_function(BuiltinFunction::Ln)))
1122 .or_else(|| f2("pow", member_function(BuiltinFunction::Pow)))
1123 .or_else(|| f2("exp", member_function(BuiltinFunction::Exp)))
1124 .or_else(|| f2("sign", member_macro(BuiltinMacroFunction::Sign)))
1125 .or_else(|| f2("to-fixed", member_function(BuiltinFunction::ToFixed)))
1126 .or_else(|| f2("to-precision", member_function(BuiltinFunction::ToPrecision)))
1127 .or_else(|| NumberWithUnitExpression(self.0).for_each_entry(ctx, f))
1128 }
1129}
1130
1131struct NumberWithUnitExpression<'a>(&'a Expression);
1133impl LookupObject for NumberWithUnitExpression<'_> {
1134 fn for_each_entry<R>(
1135 &self,
1136 ctx: &LookupCtx,
1137 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1138 ) -> Option<R> {
1139 let member_macro = |f: BuiltinMacroFunction| {
1140 LookupResult::Callable(LookupResultCallable::MemberFunction {
1141 base: self.0.clone(),
1142 base_node: ctx.current_token.clone(), member: Box::new(LookupResultCallable::Macro(f)),
1144 })
1145 };
1146
1147 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1148 None.or_else(|| f("mod", member_macro(BuiltinMacroFunction::Mod)))
1149 .or_else(|| f("clamp", member_macro(BuiltinMacroFunction::Clamp)))
1150 .or_else(|| f("abs", member_macro(BuiltinMacroFunction::Abs)))
1151 .or_else(|| f("max", member_macro(BuiltinMacroFunction::Max)))
1152 .or_else(|| f("min", member_macro(BuiltinMacroFunction::Min)))
1153 .or_else(|| {
1154 if self.0.ty() != Type::Angle {
1155 return None;
1156 }
1157 let member_function = |f: BuiltinFunction| {
1158 LookupResult::Callable(LookupResultCallable::MemberFunction {
1159 base: self.0.clone(),
1160 base_node: ctx.current_token.clone(), member: Box::new(LookupResultCallable::Callable(Callable::Builtin(f))),
1162 })
1163 };
1164 None.or_else(|| f("sin", member_function(BuiltinFunction::Sin)))
1165 .or_else(|| f("cos", member_function(BuiltinFunction::Cos)))
1166 .or_else(|| f("tan", member_function(BuiltinFunction::Tan)))
1167 })
1168 }
1169}