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