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