Skip to main content

i_slint_compiler/
lookup.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! Helper to do lookup in expressions
5
6use 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
22/// Contains information which allow to lookup identifier in expressions
23pub struct LookupCtx<'a> {
24    /// the name of the property for which this expression refers.
25    pub property_name: Option<&'a str>,
26
27    /// the type of the property for which this expression refers.
28    /// (some property come in the scope)
29    pub property_type: Type,
30
31    /// Here is the stack in which id applies. (the last element in the scope is looked up first)
32    pub component_scope: &'a [ElementRc],
33
34    /// Somewhere to report diagnostics
35    pub diag: &'a mut BuildDiagnostics,
36
37    /// The name of the arguments of the callback or function
38    pub arguments: Vec<SmolStr>,
39
40    /// The type register in which to look for Globals
41    pub type_register: &'a TypeRegister,
42
43    /// The type loader instance, which may be used to resolve relative path references
44    /// for example for img!
45    pub type_loader: Option<&'a crate::typeloader::TypeLoader>,
46
47    /// The token currently processed
48    pub current_token: Option<NodeOrToken>,
49
50    /// A stack of local variable scopes
51    pub local_variables: Vec<Vec<(SmolStr, Type)>>,
52}
53
54impl<'a> LookupCtx<'a> {
55    /// Return a context that is just suitable to build simple const expression
56    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    /// True if the element is in the same component as the scope
82    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        /// When set, this is deprecated, and the string is the new name
96        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    /// for example for `item.focus`, where `item` is the base
108    MemberFunction {
109        /// This becomes the first argument of the function call
110        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
156/// Represent an object which has properties which can be accessible
157pub trait LookupObject {
158    /// Will call the function for each entry (useful for completion)
159    /// If the function return Some, it will immediately be returned and not called further
160    fn for_each_entry<R>(
161        &self,
162        ctx: &LookupCtx,
163        f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
164    ) -> Option<R>;
165
166    /// Perform a lookup of a given identifier.
167    /// One does not have to re-implement unless we can make it faster
168    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                    // we need to strip the "local_" prefix because a lookup call will not include it
246                    &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        // "root" is just a normal id
303    }
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    // TODO: hash based lookup
348}
349
350/// In-scope properties, or model
351pub 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 the function has a ElementReference type as the first argument, that usually means it is
540            // a member function
541            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
555/// Lookup for Globals and Enum.
556struct 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                // Check if it is internal, but allow re-export (different name) eg: NativeStyleMetrics re-exported as StyleMetrics
593                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
609/// Lookup for things specific to the return type (eg: colors or enums)
610pub 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(), // Note that this is not the base_node, but the function's node
1059                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
1136/// An expression of type int or float
1137struct 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
1192/// An expression of any numerical value with an unit
1193struct 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}