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, 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 the function has a ElementReference type as the first argument, that usually means it is
543            // a member function
544            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
558/// Lookup for Globals and Enum.
559struct 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                // Check if it is internal, but allow re-export (different name) eg: NativeStyleMetrics re-exported as StyleMetrics
596                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
612/// Lookup for things specific to the return type (eg: colors or enums)
613pub 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(), // Note that this is not the base_node, but the function's node
1063                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
1140/// An expression of type int or float
1141struct 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
1199/// An expression of any numerical value with an unit
1200struct 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}