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, visibility) in crate::typeregister::reserved_properties() {
474 if visibility == PropertyVisibility::Private {
475 continue;
476 }
477 let name = SmolStr::new_static(name);
478 let e =
479 expression_from_reference(NamedReference::new(self, name.clone()), &ty, None);
480 if let Some(r) = f(&name, e) {
481 return Some(r);
482 }
483 }
484 }
485 None
486 }
487
488 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
489 let lookup_result = self.borrow().lookup_property(name);
490 if lookup_result.property_type != Type::Invalid
491 && (lookup_result.is_local_to_component
492 || lookup_result.property_visibility != PropertyVisibility::Private)
493 {
494 let deprecated = (lookup_result.resolved_name != name.as_str())
495 .then(|| lookup_result.resolved_name.to_string())
496 .or_else(|| check_extra_deprecated(self, ctx, name));
497 Some(expression_from_reference(
498 NamedReference::new(self, lookup_result.resolved_name.to_smolstr()),
499 &lookup_result.property_type,
500 deprecated,
501 ))
502 } else {
503 None
504 }
505 }
506}
507
508pub fn check_extra_deprecated(
509 elem: &ElementRc,
510 ctx: &LookupCtx<'_>,
511 name: &SmolStr,
512) -> Option<String> {
513 if crate::typeregister::DEPRECATED_ROTATION_ORIGIN_PROPERTIES.iter().any(|(p, _)| p == name) {
514 return Some(format!("transform-origin.{}", &name[name.len() - 1..]));
515 }
516 let borrow = elem.borrow();
517 (!ctx.type_register.expose_internal_types
518 && matches!(
519 borrow.enclosing_component.upgrade().unwrap().id.as_str(),
520 "StyleMetrics" | "NativeStyleMetrics"
521 )
522 && borrow
523 .debug
524 .first()
525 .and_then(|x| x.node.source_file())
526 .is_none_or(|x| x.path().starts_with("builtin:"))
527 && !name.starts_with("layout-"))
528 .then(|| format!("Palette.{name}"))
529}
530
531fn expression_from_reference(
532 n: NamedReference,
533 ty: &Type,
534 deprecated: Option<String>,
535) -> LookupResult {
536 match ty {
537 Type::Callback { .. } => Callable::Callback(n).into(),
538 Type::InferredCallback => Callable::Callback(n).into(),
539 Type::Function(function) => {
540 let base_expr = Rc::downgrade(&n.element());
541 let callable = Callable::Function(n);
542 if matches!(function.args.first(), Some(Type::ElementReference)) {
545 LookupResult::Callable(LookupResultCallable::MemberFunction {
546 base: Expression::ElementReference(base_expr),
547 base_node: None,
548 member: Box::new(LookupResultCallable::Callable(callable)),
549 })
550 } else {
551 callable.into()
552 }
553 }
554 _ => LookupResult::Expression { expression: Expression::PropertyReference(n), deprecated },
555 }
556}
557
558struct LookupType;
560impl LookupObject for LookupType {
561 fn for_each_entry<R>(
562 &self,
563 ctx: &LookupCtx,
564 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
565 ) -> Option<R> {
566 for (name, ty) in ctx.type_register.all_types() {
567 if let Some(r) = Self::from_type(ty).and_then(|e| f(&name, e)) {
568 return Some(r);
569 }
570 }
571 for (name, ty) in ctx.type_register.all_elements() {
572 if let Some(r) = Self::from_element(ty, ctx, &name).and_then(|e| f(&name, e)) {
573 return Some(r);
574 }
575 }
576 None
577 }
578
579 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
580 Self::from_type(ctx.type_register.lookup(name))
581 .or_else(|| Self::from_element(ctx.type_register.lookup_element(name).ok()?, ctx, name))
582 }
583}
584impl LookupType {
585 fn from_type(ty: Type) -> Option<LookupResult> {
586 match ty {
587 Type::Enumeration(e) => Some(LookupResult::Enumeration(e)),
588 _ => None,
589 }
590 }
591
592 fn from_element(el: ElementType, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
593 match el {
594 ElementType::Component(c) if c.is_global() => {
595 if c.root_element
597 .borrow()
598 .builtin_type()
599 .is_some_and(|x| x.is_internal && x.name == name)
600 && !ctx.type_register.expose_internal_types
601 {
602 None
603 } else {
604 Some(Expression::ElementReference(Rc::downgrade(&c.root_element)).into())
605 }
606 }
607 _ => None,
608 }
609 }
610}
611
612pub struct ReturnTypeSpecificLookup;
614impl LookupObject for ReturnTypeSpecificLookup {
615 fn for_each_entry<R>(
616 &self,
617 ctx: &LookupCtx,
618 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
619 ) -> Option<R> {
620 match ctx.return_type() {
621 Type::Color => ColorSpecific.for_each_entry(ctx, f),
622 Type::Brush => ColorSpecific.for_each_entry(ctx, f),
623 Type::Easing => EasingSpecific.for_each_entry(ctx, f),
624 Type::Enumeration(enumeration) => enumeration.clone().for_each_entry(ctx, f),
625 _ => None,
626 }
627 }
628
629 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
630 match ctx.return_type() {
631 Type::Color => ColorSpecific.lookup(ctx, name),
632 Type::Brush => ColorSpecific.lookup(ctx, name),
633 Type::Easing => EasingSpecific.lookup(ctx, name),
634 Type::Enumeration(enumeration) => enumeration.clone().lookup(ctx, name),
635 _ => None,
636 }
637 }
638}
639
640struct ColorSpecific;
641impl LookupObject for ColorSpecific {
642 fn for_each_entry<R>(
643 &self,
644 _ctx: &LookupCtx,
645 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
646 ) -> Option<R> {
647 for (name, c) in named_colors().iter() {
648 if let Some(r) = f(&SmolStr::new_static(name), Self::as_result(*c)) {
649 return Some(r);
650 }
651 }
652 None
653 }
654 fn lookup(&self, _ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
655 named_colors().get(name.as_str()).map(|c| Self::as_result(*c))
656 }
657}
658impl ColorSpecific {
659 fn as_result(value: u32) -> LookupResult {
660 Expression::Cast {
661 from: Box::new(Expression::NumberLiteral(value as f64, Unit::None)),
662 to: Type::Color,
663 }
664 .into()
665 }
666}
667
668pub struct KeysLookup;
669
670macro_rules! special_keys_lookup {
671 ($($char:literal # $name:ident # $($shifted:ident)? $(=> $($_muda:ident)? # $($qt:ident)|* # $($winit:ident $(($_pos:ident))?)|* # $($_xkb:ident)|*)? ;)*) => {
672 impl LookupObject for KeysLookup {
673 fn for_each_entry<R>(
674 &self,
675 _ctx: &LookupCtx,
676 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
677 ) -> Option<R> {
678 None
679 $(.or_else(|| {
680 let mut tmp = [0; 4];
681 f(&SmolStr::new_static(stringify!($name)), Expression::StringLiteral(SmolStr::new_inline($char.encode_utf8(&mut tmp))).into())
682 }))*
683 }
684 }
685 };
686}
687
688i_slint_common::for_each_keys!(special_keys_lookup);
689
690struct EasingSpecific;
691impl LookupObject for EasingSpecific {
692 fn for_each_entry<R>(
693 &self,
694 _ctx: &LookupCtx,
695 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
696 ) -> Option<R> {
697 use EasingCurve::CubicBezier;
698 let mut curve = |n, e| f(&SmolStr::new_static(n), Expression::EasingCurve(e).into());
699 let r = None
700 .or_else(|| curve("linear", EasingCurve::Linear))
701 .or_else(|| curve("ease-in-quad", CubicBezier(0.11, 0.0, 0.5, 0.0)))
702 .or_else(|| curve("ease-out-quad", CubicBezier(0.5, 1.0, 0.89, 1.0)))
703 .or_else(|| curve("ease-in-out-quad", CubicBezier(0.45, 0.0, 0.55, 1.0)))
704 .or_else(|| curve("ease", CubicBezier(0.25, 0.1, 0.25, 1.0)))
705 .or_else(|| curve("ease-in", CubicBezier(0.42, 0.0, 1.0, 1.0)))
706 .or_else(|| curve("ease-in-out", CubicBezier(0.42, 0.0, 0.58, 1.0)))
707 .or_else(|| curve("ease-out", CubicBezier(0.0, 0.0, 0.58, 1.0)))
708 .or_else(|| curve("ease-in-quart", CubicBezier(0.5, 0.0, 0.75, 0.0)))
709 .or_else(|| curve("ease-out-quart", CubicBezier(0.25, 1.0, 0.5, 1.0)))
710 .or_else(|| curve("ease-in-out-quart", CubicBezier(0.76, 0.0, 0.24, 1.0)))
711 .or_else(|| curve("ease-in-quint", CubicBezier(0.64, 0.0, 0.78, 0.0)))
712 .or_else(|| curve("ease-out-quint", CubicBezier(0.22, 1.0, 0.36, 1.0)))
713 .or_else(|| curve("ease-in-out-quint", CubicBezier(0.83, 0.0, 0.17, 1.0)))
714 .or_else(|| curve("ease-in-expo", CubicBezier(0.7, 0.0, 0.84, 0.0)))
715 .or_else(|| curve("ease-out-expo", CubicBezier(0.16, 1.0, 0.3, 1.0)))
716 .or_else(|| curve("ease-in-out-expo", CubicBezier(0.87, 0.0, 0.13, 1.0)))
717 .or_else(|| curve("ease-in-back", CubicBezier(0.36, 0.0, 0.66, -0.56)))
718 .or_else(|| curve("ease-out-back", CubicBezier(0.34, 1.56, 0.64, 1.0)))
719 .or_else(|| curve("ease-in-out-back", CubicBezier(0.68, -0.6, 0.32, 1.6)))
720 .or_else(|| curve("ease-in-sine", CubicBezier(0.12, 0.0, 0.39, 0.0)))
721 .or_else(|| curve("ease-out-sine", CubicBezier(0.61, 1.0, 0.88, 1.0)))
722 .or_else(|| curve("ease-in-out-sine", CubicBezier(0.37, 0.0, 0.63, 1.0)))
723 .or_else(|| curve("ease-in-circ", CubicBezier(0.55, 0.0, 1.0, 0.45)))
724 .or_else(|| curve("ease-out-circ", CubicBezier(0.0, 0.55, 0.45, 1.0)))
725 .or_else(|| curve("ease-in-out-circ", CubicBezier(0.85, 0.0, 0.15, 1.0)))
726 .or_else(|| curve("ease-in-elastic", EasingCurve::EaseInElastic))
727 .or_else(|| curve("ease-out-elastic", EasingCurve::EaseOutElastic))
728 .or_else(|| curve("ease-in-out-elastic", EasingCurve::EaseInOutElastic))
729 .or_else(|| curve("ease-in-bounce", EasingCurve::EaseInBounce))
730 .or_else(|| curve("ease-out-bounce", EasingCurve::EaseOutBounce))
731 .or_else(|| curve("ease-in-out-bounce", EasingCurve::EaseInOutBounce));
732 r.or_else(|| {
733 f(&SmolStr::new_static("cubic-bezier"), BuiltinMacroFunction::CubicBezier.into())
734 })
735 }
736}
737
738struct FontWeightLookup;
739impl LookupObject for FontWeightLookup {
740 fn for_each_entry<R>(
741 &self,
742 _ctx: &LookupCtx,
743 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
744 ) -> Option<R> {
745 let mut weight =
746 |n, v: f64| f(&SmolStr::new_static(n), Expression::NumberLiteral(v, Unit::None).into());
747 None.or_else(|| weight("thin", 100.0))
748 .or_else(|| weight("extra-light", 200.0))
749 .or_else(|| weight("light", 300.0))
750 .or_else(|| weight("normal", 400.0))
751 .or_else(|| weight("medium", 500.0))
752 .or_else(|| weight("semi-bold", 600.0))
753 .or_else(|| weight("bold", 700.0))
754 .or_else(|| weight("extra-bold", 800.0))
755 .or_else(|| weight("black", 900.0))
756 }
757}
758
759impl LookupObject for Rc<Enumeration> {
760 fn for_each_entry<R>(
761 &self,
762 _ctx: &LookupCtx,
763 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
764 ) -> Option<R> {
765 for (value, name) in self.values.iter().enumerate() {
766 if let Some(r) = f(
767 name,
768 Expression::EnumerationValue(EnumerationValue { value, enumeration: self.clone() })
769 .into(),
770 ) {
771 return Some(r);
772 }
773 }
774 None
775 }
776}
777
778struct MathFunctions;
779impl LookupObject for MathFunctions {
780 fn for_each_entry<R>(
781 &self,
782 _ctx: &LookupCtx,
783 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
784 ) -> Option<R> {
785 let mut f = |n, e| f(&SmolStr::new_static(n), e);
786 let b = |b| LookupResult::from(Callable::Builtin(b));
787 None.or_else(|| f("mod", BuiltinMacroFunction::Mod.into()))
788 .or_else(|| f("round", b(BuiltinFunction::Round)))
789 .or_else(|| f("ceil", b(BuiltinFunction::Ceil)))
790 .or_else(|| f("floor", b(BuiltinFunction::Floor)))
791 .or_else(|| f("clamp", BuiltinMacroFunction::Clamp.into()))
792 .or_else(|| f("abs", BuiltinMacroFunction::Abs.into()))
793 .or_else(|| f("sqrt", b(BuiltinFunction::Sqrt)))
794 .or_else(|| f("max", BuiltinMacroFunction::Max.into()))
795 .or_else(|| f("min", BuiltinMacroFunction::Min.into()))
796 .or_else(|| f("sin", b(BuiltinFunction::Sin)))
797 .or_else(|| f("cos", b(BuiltinFunction::Cos)))
798 .or_else(|| f("tan", b(BuiltinFunction::Tan)))
799 .or_else(|| f("asin", b(BuiltinFunction::ASin)))
800 .or_else(|| f("acos", b(BuiltinFunction::ACos)))
801 .or_else(|| f("atan", b(BuiltinFunction::ATan)))
802 .or_else(|| f("atan2", b(BuiltinFunction::ATan2)))
803 .or_else(|| f("log", b(BuiltinFunction::Log)))
804 .or_else(|| f("ln", b(BuiltinFunction::Ln)))
805 .or_else(|| f("pow", b(BuiltinFunction::Pow)))
806 .or_else(|| f("exp", b(BuiltinFunction::Exp)))
807 .or_else(|| f("sign", BuiltinMacroFunction::Sign.into()))
808 }
809}
810
811struct SlintInternal;
812impl LookupObject for SlintInternal {
813 fn for_each_entry<R>(
814 &self,
815 ctx: &LookupCtx,
816 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
817 ) -> Option<R> {
818 let sl = || ctx.current_token.as_ref().map(|t| t.to_source_location());
819 let mut f = |n, e: LookupResult| f(&SmolStr::new_static(n), e);
820 let b = |b| LookupResult::from(Callable::Builtin(b));
821 None.or_else(|| {
822 let style = ctx.type_loader.and_then(|tl| tl.compiler_config.style.as_ref());
823 f(
824 "color-scheme",
825 if style.is_some_and(|s| s.ends_with("-light")) {
826 let e = crate::typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone());
827 Expression::EnumerationValue(e.try_value_from_string("light").unwrap())
828 } else if style.is_some_and(|s| s.ends_with("-dark")) {
829 let e = crate::typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone());
830 Expression::EnumerationValue(e.try_value_from_string("dark").unwrap())
831 } else {
832 Expression::FunctionCall {
833 function: BuiltinFunction::ColorScheme.into(),
834 arguments: Vec::new(),
835 source_location: sl(),
836 }
837 }
838 .into(),
839 )
840 })
841 .or_else(|| {
842 f(
843 "accent-color",
844 Expression::FunctionCall {
845 function: BuiltinFunction::AccentColor.into(),
846 arguments: Vec::new(),
847 source_location: sl(),
848 }
849 .into(),
850 )
851 })
852 .or_else(|| {
853 f(
854 "use-24-hour-format",
855 Expression::FunctionCall {
856 function: BuiltinFunction::Use24HourFormat.into(),
857 arguments: Vec::new(),
858 source_location: sl(),
859 }
860 .into(),
861 )
862 })
863 .or_else(|| f("month-day-count", b(BuiltinFunction::MonthDayCount)))
864 .or_else(|| f("month-offset", b(BuiltinFunction::MonthOffset)))
865 .or_else(|| f("format-date", b(BuiltinFunction::FormatDate)))
866 .or_else(|| f("date-now", b(BuiltinFunction::DateNow)))
867 .or_else(|| f("valid-date", b(BuiltinFunction::ValidDate)))
868 .or_else(|| f("parse-date", b(BuiltinFunction::ParseDate)))
869 }
870}
871
872struct ColorFunctions;
873impl LookupObject for ColorFunctions {
874 fn for_each_entry<R>(
875 &self,
876 _ctx: &LookupCtx,
877 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
878 ) -> Option<R> {
879 let mut f = |n, m| f(&SmolStr::new_static(n), LookupResult::from(m));
880 None.or_else(|| f("rgb", BuiltinMacroFunction::Rgb))
881 .or_else(|| f("rgba", BuiltinMacroFunction::Rgb))
882 .or_else(|| f("hsv", BuiltinMacroFunction::Hsv))
883 .or_else(|| f("oklch", BuiltinMacroFunction::Oklch))
884 }
885}
886
887struct BuiltinFunctionLookup;
888impl LookupObject for BuiltinFunctionLookup {
889 fn for_each_entry<R>(
890 &self,
891 ctx: &LookupCtx,
892 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
893 ) -> Option<R> {
894 (MathFunctions, ColorFunctions)
895 .for_each_entry(ctx, f)
896 .or_else(|| f(&SmolStr::new_static("debug"), BuiltinMacroFunction::Debug.into()))
897 .or_else(|| {
898 f(&SmolStr::new_static("animation-tick"), BuiltinFunction::AnimationTick.into())
899 })
900 }
901}
902
903struct BuiltinNamespaceLookup;
904impl LookupObject for BuiltinNamespaceLookup {
905 fn for_each_entry<R>(
906 &self,
907 ctx: &LookupCtx,
908 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
909 ) -> Option<R> {
910 let mut f = |s, res| f(&SmolStr::new_static(s), res);
911 None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors)))
912 .or_else(|| f("Easing", LookupResult::Namespace(BuiltinNamespace::Easing)))
913 .or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math)))
914 .or_else(|| f("Key", LookupResult::Namespace(BuiltinNamespace::Key)))
915 .or_else(|| f("FontWeight", LookupResult::Namespace(BuiltinNamespace::FontWeight)))
916 .or_else(|| {
917 if ctx.type_register.expose_internal_types {
918 f("SlintInternal", LookupResult::Namespace(BuiltinNamespace::SlintInternal))
919 } else {
920 None
921 }
922 })
923 }
924}
925
926pub fn global_lookup() -> impl LookupObject {
927 (
928 LocalVariableLookup,
929 (
930 ArgumentsLookup,
931 (
932 SpecialIdLookup,
933 (
934 IdLookup,
935 (
936 InScopeLookup,
937 (
938 LookupType,
939 (
940 BuiltinNamespaceLookup,
941 (ReturnTypeSpecificLookup, BuiltinFunctionLookup),
942 ),
943 ),
944 ),
945 ),
946 ),
947 ),
948 )
949}
950
951impl LookupObject for Expression {
952 fn for_each_entry<R>(
953 &self,
954 ctx: &LookupCtx,
955 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
956 ) -> Option<R> {
957 match self {
958 Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f),
959 _ => match self.ty() {
960 Type::Struct(s) => {
961 for name in s.fields.keys() {
962 if let Some(r) = f(
963 name,
964 Expression::StructFieldAccess {
965 base: Box::new(self.clone()),
966 name: name.clone(),
967 }
968 .into(),
969 ) {
970 return Some(r);
971 }
972 }
973 None
974 }
975 Type::String => StringExpression(self).for_each_entry(ctx, f),
976 Type::Brush | Type::Color => ColorExpression(self).for_each_entry(ctx, f),
977 Type::Image => ImageExpression(self).for_each_entry(ctx, f),
978 Type::Array(_) => ArrayExpression(self).for_each_entry(ctx, f),
979 Type::Float32 | Type::Int32 | Type::Percent => {
980 NumberExpression(self).for_each_entry(ctx, f)
981 }
982 Type::Keys => KeysExpression(self).for_each_entry(ctx, f),
983 ty if ty.as_unit_product().is_some() => {
984 NumberWithUnitExpression(self).for_each_entry(ctx, f)
985 }
986 _ => None,
987 },
988 }
989 }
990
991 fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
992 match self {
993 Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name),
994 _ => match self.ty() {
995 Type::Struct(s) => s.fields.contains_key(name).then(|| {
996 LookupResult::from(Expression::StructFieldAccess {
997 base: Box::new(self.clone()),
998 name: name.clone(),
999 })
1000 }),
1001 Type::String => StringExpression(self).lookup(ctx, name),
1002 Type::Brush | Type::Color => ColorExpression(self).lookup(ctx, name),
1003 Type::Image => ImageExpression(self).lookup(ctx, name),
1004 Type::Array(_) => ArrayExpression(self).lookup(ctx, name),
1005 Type::Float32 | Type::Int32 | Type::Percent => {
1006 NumberExpression(self).lookup(ctx, name)
1007 }
1008 Type::Keys => KeysExpression(self).lookup(ctx, name),
1009 ty if ty.as_unit_product().is_some() => {
1010 NumberWithUnitExpression(self).lookup(ctx, name)
1011 }
1012 _ => None,
1013 },
1014 }
1015 }
1016}
1017
1018struct StringExpression<'a>(&'a Expression);
1019impl LookupObject for StringExpression<'_> {
1020 fn for_each_entry<R>(
1021 &self,
1022 ctx: &LookupCtx,
1023 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1024 ) -> Option<R> {
1025 let member_function = builtin_member_function_generator(self.0, ctx);
1026 let function_call = |f: BuiltinFunction| {
1027 LookupResult::from(Expression::FunctionCall {
1028 function: Callable::Builtin(f),
1029 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1030 arguments: vec![self.0.clone()],
1031 })
1032 };
1033
1034 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1035 None.or_else(|| f("is-float", member_function(BuiltinFunction::StringIsFloat)))
1036 .or_else(|| f("to-float", member_function(BuiltinFunction::StringToFloat)))
1037 .or_else(|| f("is-empty", function_call(BuiltinFunction::StringIsEmpty)))
1038 .or_else(|| f("character-count", function_call(BuiltinFunction::StringCharacterCount)))
1039 .or_else(|| f("to-lowercase", member_function(BuiltinFunction::StringToLowercase)))
1040 .or_else(|| f("to-uppercase", member_function(BuiltinFunction::StringToUppercase)))
1041 }
1042}
1043
1044struct ColorExpression<'a>(&'a Expression);
1045impl LookupObject for ColorExpression<'_> {
1046 fn for_each_entry<R>(
1047 &self,
1048 ctx: &LookupCtx,
1049 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1050 ) -> Option<R> {
1051 let member_function = |f: BuiltinFunction| {
1052 let base = if (f == BuiltinFunction::ColorHsvaStruct
1053 || f == BuiltinFunction::ColorOklchStruct)
1054 && self.0.ty() == Type::Brush
1055 {
1056 Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
1057 } else {
1058 self.0.clone()
1059 };
1060 LookupResult::Callable(LookupResultCallable::MemberFunction {
1061 base,
1062 base_node: ctx.current_token.clone(), member: Box::new(LookupResultCallable::Callable(Callable::Builtin(f))),
1064 })
1065 };
1066 let field_access = |f: &'static str| {
1067 let base = if self.0.ty() == Type::Brush {
1068 Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
1069 } else {
1070 self.0.clone()
1071 };
1072 LookupResult::from(Expression::StructFieldAccess {
1073 base: Box::new(Expression::FunctionCall {
1074 function: BuiltinFunction::ColorRgbaStruct.into(),
1075 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1076 arguments: vec![base],
1077 }),
1078 name: SmolStr::new_static(f),
1079 })
1080 };
1081
1082 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1083 None.or_else(|| f("red", field_access("red")))
1084 .or_else(|| f("green", field_access("green")))
1085 .or_else(|| f("blue", field_access("blue")))
1086 .or_else(|| f("alpha", field_access("alpha")))
1087 .or_else(|| f("to-hsv", member_function(BuiltinFunction::ColorHsvaStruct)))
1088 .or_else(|| f("to-oklch", member_function(BuiltinFunction::ColorOklchStruct)))
1089 .or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
1090 .or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
1091 .or_else(|| f("transparentize", member_function(BuiltinFunction::ColorTransparentize)))
1092 .or_else(|| f("with-alpha", member_function(BuiltinFunction::ColorWithAlpha)))
1093 .or_else(|| f("mix", member_function(BuiltinFunction::ColorMix)))
1094 }
1095}
1096
1097struct ImageExpression<'a>(&'a Expression);
1098impl LookupObject for ImageExpression<'_> {
1099 fn for_each_entry<R>(
1100 &self,
1101 ctx: &LookupCtx,
1102 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1103 ) -> Option<R> {
1104 let field_access = |f: &str| {
1105 LookupResult::from(Expression::StructFieldAccess {
1106 base: Box::new(Expression::FunctionCall {
1107 function: BuiltinFunction::ImageSize.into(),
1108 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1109 arguments: vec![self.0.clone()],
1110 }),
1111 name: f.into(),
1112 })
1113 };
1114 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1115 None.or_else(|| f("width", field_access("width")))
1116 .or_else(|| f("height", field_access("height")))
1117 }
1118}
1119
1120struct ArrayExpression<'a>(&'a Expression);
1121impl LookupObject for ArrayExpression<'_> {
1122 fn for_each_entry<R>(
1123 &self,
1124 ctx: &LookupCtx,
1125 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1126 ) -> Option<R> {
1127 let member_function = |f: BuiltinFunction| {
1128 LookupResult::from(Expression::FunctionCall {
1129 function: Callable::Builtin(f),
1130 source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
1131 arguments: vec![self.0.clone()],
1132 })
1133 };
1134 None.or_else(|| {
1135 f(&SmolStr::new_static("length"), member_function(BuiltinFunction::ArrayLength))
1136 })
1137 }
1138}
1139
1140struct NumberExpression<'a>(&'a Expression);
1142impl LookupObject for NumberExpression<'_> {
1143 fn for_each_entry<R>(
1144 &self,
1145 ctx: &LookupCtx,
1146 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1147 ) -> Option<R> {
1148 let member_function = builtin_member_function_generator(self.0, ctx);
1149 let mut member_macro = member_macro_generator(self.0.clone(), ctx.current_token.clone());
1150
1151 let mut f2 = |s, res| f(&SmolStr::new_static(s), res);
1152 None.or_else(|| f2("round", member_function(BuiltinFunction::Round)))
1153 .or_else(|| f2("ceil", member_function(BuiltinFunction::Ceil)))
1154 .or_else(|| f2("floor", member_function(BuiltinFunction::Floor)))
1155 .or_else(|| f2("sqrt", member_function(BuiltinFunction::Sqrt)))
1156 .or_else(|| f2("asin", member_function(BuiltinFunction::ASin)))
1157 .or_else(|| f2("acos", member_function(BuiltinFunction::ACos)))
1158 .or_else(|| f2("atan", member_function(BuiltinFunction::ATan)))
1159 .or_else(|| f2("log", member_function(BuiltinFunction::Log)))
1160 .or_else(|| f2("ln", member_function(BuiltinFunction::Ln)))
1161 .or_else(|| f2("pow", member_function(BuiltinFunction::Pow)))
1162 .or_else(|| f2("exp", member_function(BuiltinFunction::Exp)))
1163 .or_else(|| f2("sign", member_macro(BuiltinMacroFunction::Sign)))
1164 .or_else(|| f2("to-fixed", member_function(BuiltinFunction::ToFixed)))
1165 .or_else(|| f2("to-precision", member_function(BuiltinFunction::ToPrecision)))
1166 .or_else(|| {
1167 f2("to-string-unlocalized", member_function(BuiltinFunction::ToStringUnlocalized))
1168 })
1169 .or_else(|| NumberWithUnitExpression(self.0).for_each_entry(ctx, f))
1170 }
1171}
1172
1173fn builtin_member_function_generator<'a>(
1174 base: &'a Expression,
1175 ctx: &'a LookupCtx,
1176) -> impl Fn(BuiltinFunction) -> LookupResult {
1177 move |func: BuiltinFunction| {
1178 LookupResult::Callable(LookupResultCallable::MemberFunction {
1179 base: base.clone(),
1180 base_node: ctx.current_token.clone(),
1181 member: Box::new(LookupResultCallable::Callable(Callable::Builtin(func))),
1182 })
1183 }
1184}
1185
1186fn member_macro_generator(
1187 base: Expression,
1188 base_node: Option<NodeOrToken>,
1189) -> impl FnMut(BuiltinMacroFunction) -> LookupResult {
1190 move |func: BuiltinMacroFunction| {
1191 LookupResult::Callable(LookupResultCallable::MemberFunction {
1192 base: base.clone(),
1193 base_node: base_node.clone(),
1194 member: Box::new(LookupResultCallable::Macro(func)),
1195 })
1196 }
1197}
1198
1199struct NumberWithUnitExpression<'a>(&'a Expression);
1201impl LookupObject for NumberWithUnitExpression<'_> {
1202 fn for_each_entry<R>(
1203 &self,
1204 ctx: &LookupCtx,
1205 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1206 ) -> Option<R> {
1207 let mut member_macro = member_macro_generator(self.0.clone(), ctx.current_token.clone());
1208 let member_function = builtin_member_function_generator(self.0, ctx);
1209 let mut f = |s, res| f(&SmolStr::new_static(s), res);
1210 None.or_else(|| f("mod", member_macro(BuiltinMacroFunction::Mod)))
1211 .or_else(|| f("clamp", member_macro(BuiltinMacroFunction::Clamp)))
1212 .or_else(|| f("abs", member_macro(BuiltinMacroFunction::Abs)))
1213 .or_else(|| f("max", member_macro(BuiltinMacroFunction::Max)))
1214 .or_else(|| f("min", member_macro(BuiltinMacroFunction::Min)))
1215 .or_else(|| {
1216 if self.0.ty() != Type::Angle {
1217 return None;
1218 }
1219 None.or_else(|| f("sin", member_function(BuiltinFunction::Sin)))
1220 .or_else(|| f("cos", member_function(BuiltinFunction::Cos)))
1221 .or_else(|| f("tan", member_function(BuiltinFunction::Tan)))
1222 })
1223 }
1224}
1225
1226struct KeysExpression<'a>(&'a Expression);
1227
1228impl LookupObject for KeysExpression<'_> {
1229 fn for_each_entry<R>(
1230 &self,
1231 ctx: &LookupCtx,
1232 f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
1233 ) -> Option<R> {
1234 let member_function = builtin_member_function_generator(self.0, ctx);
1235 None.or_else(|| {
1236 f(&SmolStr::new_static("to-string"), member_function(BuiltinFunction::KeysToString))
1237 })
1238 }
1239}