sixtyfps_compilerlib/
lookup.rs

1// Copyright © SixtyFPS GmbH <info@sixtyfps.io>
2// SPDX-License-Identifier: (GPL-3.0-only OR LicenseRef-SixtyFPS-commercial)
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, EasingCurve, Expression, Unit,
11};
12use crate::langtype::{Enumeration, EnumerationValue, Type};
13use crate::namedreference::NamedReference;
14use crate::object_tree::{find_parent_element, ElementRc};
15use crate::parser::NodeOrToken;
16use crate::typeregister::TypeRegister;
17
18/// Contains information which allow to lookup identifier in expressions
19pub struct LookupCtx<'a> {
20    /// the name of the property for which this expression refers.
21    pub property_name: Option<&'a str>,
22
23    /// the type of the property for which this expression refers.
24    /// (some property come in the scope)
25    pub property_type: Type,
26
27    /// Here is the stack in which id applies
28    pub component_scope: &'a [ElementRc],
29
30    /// Somewhere to report diagnostics
31    pub diag: &'a mut BuildDiagnostics,
32
33    /// The name of the arguments of the callback or function
34    pub arguments: Vec<String>,
35
36    /// The type register in which to look for Globals
37    pub type_register: &'a TypeRegister,
38
39    /// The type loader instance, which may be used to resolve relative path references
40    /// for example for img!
41    pub type_loader: Option<&'a crate::typeloader::TypeLoader<'a>>,
42
43    /// The token currently processed
44    pub current_token: Option<NodeOrToken>,
45}
46
47impl<'a> LookupCtx<'a> {
48    /// Return a context that is just suitable to build simple const expression
49    pub fn empty_context(type_register: &'a TypeRegister, diag: &'a mut BuildDiagnostics) -> Self {
50        Self {
51            property_name: Default::default(),
52            property_type: Default::default(),
53            component_scope: Default::default(),
54            diag,
55            arguments: Default::default(),
56            type_register,
57            type_loader: None,
58            current_token: None,
59        }
60    }
61
62    pub fn return_type(&self) -> &Type {
63        if let Type::Callback { return_type, .. } = &self.property_type {
64            return_type.as_ref().map_or(&Type::Void, |b| &(**b))
65        } else {
66            &self.property_type
67        }
68    }
69}
70
71pub enum LookupResult {
72    Expression {
73        expression: Expression,
74        /// When set, this is deprecated, and the string is the new name
75        deprecated: Option<String>,
76    },
77    Enumeration(Rc<Enumeration>),
78    Namespace(BuiltinNamespace),
79}
80
81pub enum BuiltinNamespace {
82    Colors,
83    Math,
84    Keys,
85}
86
87impl From<Expression> for LookupResult {
88    fn from(expression: Expression) -> Self {
89        Self::Expression { expression, deprecated: None }
90    }
91}
92
93impl LookupResult {
94    pub fn deprecated(&self) -> Option<&str> {
95        match self {
96            Self::Expression { deprecated: Some(x), .. } => Some(x.as_str()),
97            _ => None,
98        }
99    }
100}
101
102/// Represent an object which has properties which can be accessible
103pub trait LookupObject {
104    /// Will call the function for each entry (useful for completion)
105    /// If the function return Some, it will immediately be returned and not called further
106    fn for_each_entry<R>(
107        &self,
108        ctx: &LookupCtx,
109        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
110    ) -> Option<R>;
111
112    /// Perform a lookup of a given identifier.
113    /// One does not have to re-implement unless we can make it faster
114    fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
115        self.for_each_entry(ctx, &mut |prop, expr| (prop == name).then(|| expr))
116    }
117}
118
119impl<T1: LookupObject, T2: LookupObject> LookupObject for (T1, T2) {
120    fn for_each_entry<R>(
121        &self,
122        ctx: &LookupCtx,
123        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
124    ) -> Option<R> {
125        self.0.for_each_entry(ctx, f).or_else(|| self.1.for_each_entry(ctx, f))
126    }
127
128    fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
129        self.0.lookup(ctx, name).or_else(|| self.1.lookup(ctx, name))
130    }
131}
132
133impl LookupObject for LookupResult {
134    fn for_each_entry<R>(
135        &self,
136        ctx: &LookupCtx,
137        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
138    ) -> Option<R> {
139        match self {
140            LookupResult::Expression { expression, .. } => expression.for_each_entry(ctx, f),
141            LookupResult::Enumeration(e) => e.for_each_entry(ctx, f),
142            LookupResult::Namespace(BuiltinNamespace::Colors) => {
143                (ColorSpecific, ColorFunctions).for_each_entry(ctx, f)
144            }
145            LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.for_each_entry(ctx, f),
146            LookupResult::Namespace(BuiltinNamespace::Keys) => KeysLookup.for_each_entry(ctx, f),
147        }
148    }
149
150    fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
151        match self {
152            LookupResult::Expression { expression, .. } => expression.lookup(ctx, name),
153            LookupResult::Enumeration(e) => e.lookup(ctx, name),
154            LookupResult::Namespace(BuiltinNamespace::Colors) => {
155                (ColorSpecific, ColorFunctions).lookup(ctx, name)
156            }
157            LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.lookup(ctx, name),
158            LookupResult::Namespace(BuiltinNamespace::Keys) => KeysLookup.lookup(ctx, name),
159        }
160    }
161}
162
163struct ArgumentsLookup;
164impl LookupObject for ArgumentsLookup {
165    fn for_each_entry<R>(
166        &self,
167        ctx: &LookupCtx,
168        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
169    ) -> Option<R> {
170        let args = match &ctx.property_type {
171            Type::Callback { args, .. } | Type::Function { args, .. } => args,
172            _ => return None,
173        };
174        for (index, (name, ty)) in ctx.arguments.iter().zip(args.iter()).enumerate() {
175            if let Some(r) =
176                f(name, Expression::FunctionParameterReference { index, ty: ty.clone() }.into())
177            {
178                return Some(r);
179            }
180        }
181        None
182    }
183}
184
185struct SpecialIdLookup;
186impl LookupObject for SpecialIdLookup {
187    fn for_each_entry<R>(
188        &self,
189        ctx: &LookupCtx,
190        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
191    ) -> Option<R> {
192        let last = ctx.component_scope.last();
193        None.or_else(|| f("self", Expression::ElementReference(Rc::downgrade(last?)).into()))
194            .or_else(|| {
195                f(
196                    "parent",
197                    Expression::ElementReference(Rc::downgrade(&find_parent_element(last?)?))
198                        .into(),
199                )
200            })
201            .or_else(|| f("true", Expression::BoolLiteral(true).into()))
202            .or_else(|| f("false", Expression::BoolLiteral(false).into()))
203        // "root" is just a normal id
204    }
205}
206
207struct IdLookup;
208impl LookupObject for IdLookup {
209    fn for_each_entry<R>(
210        &self,
211        ctx: &LookupCtx,
212        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
213    ) -> Option<R> {
214        fn visit<R>(
215            roots: &[ElementRc],
216            f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
217        ) -> Option<R> {
218            for e in roots.iter().rev() {
219                if !e.borrow().id.is_empty() {
220                    if let Some(r) =
221                        f(&e.borrow().id, Expression::ElementReference(Rc::downgrade(e)).into())
222                    {
223                        return Some(r);
224                    }
225                }
226                for x in &e.borrow().children {
227                    if x.borrow().repeated.is_some() {
228                        continue;
229                    }
230                    if let Some(r) = visit(&[x.clone()], f) {
231                        return Some(r);
232                    }
233                }
234            }
235            None
236        }
237        visit(ctx.component_scope, f)
238    }
239    // TODO: hash based lookup
240}
241
242struct InScopeLookup;
243impl LookupObject for InScopeLookup {
244    fn for_each_entry<R>(
245        &self,
246        ctx: &LookupCtx,
247        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
248    ) -> Option<R> {
249        for elem in ctx.component_scope.iter().rev() {
250            if let Some(repeated) = &elem.borrow().repeated {
251                if !repeated.index_id.is_empty() {
252                    if let Some(r) = f(
253                        &repeated.index_id,
254                        Expression::RepeaterIndexReference { element: Rc::downgrade(elem) }.into(),
255                    ) {
256                        return Some(r);
257                    }
258                }
259                if !repeated.model_data_id.is_empty() {
260                    if let Some(r) = f(
261                        &repeated.model_data_id,
262                        Expression::RepeaterIndexReference { element: Rc::downgrade(elem) }.into(),
263                    ) {
264                        return Some(r);
265                    }
266                }
267            }
268
269            if let Some(r) = elem.for_each_entry(ctx, f) {
270                return Some(r);
271            }
272        }
273        None
274    }
275
276    fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
277        if name.is_empty() {
278            return None;
279        }
280        for elem in ctx.component_scope.iter().rev() {
281            if let Some(repeated) = &elem.borrow().repeated {
282                if repeated.index_id == name {
283                    return Some(LookupResult::from(Expression::RepeaterIndexReference {
284                        element: Rc::downgrade(elem),
285                    }));
286                }
287                if repeated.model_data_id == name {
288                    return Some(LookupResult::from(Expression::RepeaterModelReference {
289                        element: Rc::downgrade(elem),
290                    }));
291                }
292            }
293
294            if let Some(r) = elem.lookup(ctx, name) {
295                return Some(r);
296            }
297        }
298        None
299    }
300}
301
302impl LookupObject for ElementRc {
303    fn for_each_entry<R>(
304        &self,
305        _ctx: &LookupCtx,
306        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
307    ) -> Option<R> {
308        for (name, prop) in &self.borrow().property_declarations {
309            let e = expression_from_reference(NamedReference::new(self, name), &prop.property_type);
310            if let Some(r) = f(name, e.into()) {
311                return Some(r);
312            }
313        }
314        let list = self.borrow().base_type.property_list();
315        for (name, ty) in list {
316            let e = expression_from_reference(NamedReference::new(self, &name), &ty);
317            if let Some(r) = f(&name, e.into()) {
318                return Some(r);
319            }
320        }
321        for (name, ty) in crate::typeregister::reserved_properties() {
322            let e = expression_from_reference(NamedReference::new(self, name), &ty);
323            if let Some(r) = f(name, e.into()) {
324                return Some(r);
325            }
326        }
327        None
328    }
329
330    fn lookup(&self, _ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
331        let crate::langtype::PropertyLookupResult { resolved_name, property_type } =
332            self.borrow().lookup_property(name);
333        (property_type != Type::Invalid).then(|| LookupResult::Expression {
334            expression: expression_from_reference(
335                NamedReference::new(self, &resolved_name),
336                &property_type,
337            ),
338            deprecated: (resolved_name != name).then(|| resolved_name.to_string()),
339        })
340    }
341}
342
343fn expression_from_reference(n: NamedReference, ty: &Type) -> Expression {
344    if matches!(ty, Type::Callback { .. }) {
345        Expression::CallbackReference(n)
346    } else {
347        Expression::PropertyReference(n)
348    }
349}
350
351/// Lookup for Globals and Enum.
352/// Note: for enums, the expression's value is `usize::MAX`
353struct LookupType;
354impl LookupObject for LookupType {
355    fn for_each_entry<R>(
356        &self,
357        ctx: &LookupCtx,
358        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
359    ) -> Option<R> {
360        for (name, ty) in ctx.type_register.all_types() {
361            if let Some(r) = Self::as_result(ty).and_then(|e| f(&name, e)) {
362                return Some(r);
363            }
364        }
365        None
366    }
367
368    fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
369        Self::as_result(ctx.type_register.lookup(name)).map(LookupResult::from)
370    }
371}
372impl LookupType {
373    fn as_result(ty: Type) -> Option<LookupResult> {
374        match ty {
375            Type::Component(c) if c.is_global() => {
376                Some(Expression::ElementReference(Rc::downgrade(&c.root_element)).into())
377            }
378            Type::Enumeration(e) => Some(LookupResult::Enumeration(e)),
379            _ => None,
380        }
381    }
382}
383
384struct ReturnTypeSpecificLookup;
385impl LookupObject for ReturnTypeSpecificLookup {
386    fn for_each_entry<R>(
387        &self,
388        ctx: &LookupCtx,
389        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
390    ) -> Option<R> {
391        match ctx.return_type() {
392            Type::Color => ColorSpecific.for_each_entry(ctx, f),
393            Type::Brush => ColorSpecific.for_each_entry(ctx, f),
394            Type::Easing => EasingSpecific.for_each_entry(ctx, f),
395            Type::Enumeration(enumeration) => enumeration.clone().for_each_entry(ctx, f),
396            _ => None,
397        }
398    }
399
400    fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
401        match ctx.return_type() {
402            Type::Color => ColorSpecific.lookup(ctx, name),
403            Type::Brush => ColorSpecific.lookup(ctx, name),
404            Type::Easing => EasingSpecific.lookup(ctx, name),
405            Type::Enumeration(enumeration) => enumeration.clone().lookup(ctx, name),
406            _ => None,
407        }
408    }
409}
410
411struct ColorSpecific;
412impl LookupObject for ColorSpecific {
413    fn for_each_entry<R>(
414        &self,
415        _ctx: &LookupCtx,
416        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
417    ) -> Option<R> {
418        for (name, c) in css_color_parser2::NAMED_COLORS.iter() {
419            if let Some(r) = f(name, Self::as_result(*c)) {
420                return Some(r);
421            }
422        }
423        None
424    }
425    fn lookup(&self, _ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
426        css_color_parser2::NAMED_COLORS.get(name).map(|c| Self::as_result(*c))
427    }
428}
429impl ColorSpecific {
430    fn as_result(c: css_color_parser2::Color) -> LookupResult {
431        let value =
432            ((c.a as u32 * 255) << 24) | ((c.r as u32) << 16) | ((c.g as u32) << 8) | (c.b as u32);
433        Expression::Cast {
434            from: Box::new(Expression::NumberLiteral(value as f64, Unit::None)),
435            to: Type::Color,
436        }
437        .into()
438    }
439}
440
441struct KeysLookup;
442
443macro_rules! special_keys_lookup {
444    ($($char:literal # $name:ident # $($qt:ident)|* # $($winit:ident)|* ;)*) => {
445        impl LookupObject for KeysLookup {
446            fn for_each_entry<R>(
447                &self,
448                _ctx: &LookupCtx,
449                f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
450            ) -> Option<R> {
451                None
452                $(.or_else(|| {
453                    f(stringify!($name), Expression::StringLiteral($char.into()).into())
454                }))*
455            }
456        }
457    };
458}
459
460sixtyfps_common::for_each_special_keys!(special_keys_lookup);
461
462struct EasingSpecific;
463impl LookupObject for EasingSpecific {
464    fn for_each_entry<R>(
465        &self,
466        ctx: &LookupCtx,
467        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
468    ) -> Option<R> {
469        use EasingCurve::CubicBezier;
470        None.or_else(|| f("linear", Expression::EasingCurve(EasingCurve::Linear).into()))
471            .or_else(|| {
472                f("ease", Expression::EasingCurve(CubicBezier(0.25, 0.1, 0.25, 1.0)).into())
473            })
474            .or_else(|| {
475                f("ease-in", Expression::EasingCurve(CubicBezier(0.42, 0.0, 1.0, 1.0)).into())
476            })
477            .or_else(|| {
478                f("ease-in-out", Expression::EasingCurve(CubicBezier(0.42, 0.0, 0.58, 1.0)).into())
479            })
480            .or_else(|| {
481                f("ease-out", Expression::EasingCurve(CubicBezier(0.0, 0.0, 0.58, 1.0)).into())
482            })
483            .or_else(|| {
484                f(
485                    "cubic-bezier",
486                    Expression::BuiltinMacroReference(
487                        BuiltinMacroFunction::CubicBezier,
488                        ctx.current_token.clone(),
489                    )
490                    .into(),
491                )
492            })
493    }
494}
495
496impl LookupObject for Rc<Enumeration> {
497    fn for_each_entry<R>(
498        &self,
499        _ctx: &LookupCtx,
500        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
501    ) -> Option<R> {
502        for (value, name) in self.values.iter().enumerate() {
503            if let Some(r) = f(
504                name,
505                Expression::EnumerationValue(EnumerationValue { value, enumeration: self.clone() })
506                    .into(),
507            ) {
508                return Some(r);
509            }
510        }
511        None
512    }
513}
514
515struct MathFunctions;
516impl LookupObject for MathFunctions {
517    fn for_each_entry<R>(
518        &self,
519        ctx: &LookupCtx,
520        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
521    ) -> Option<R> {
522        use Expression::{BuiltinFunctionReference, BuiltinMacroReference};
523        let t = &ctx.current_token;
524        let sl = || t.as_ref().map(|t| t.to_source_location());
525        let mut f = |n, e: Expression| f(n, e.into());
526        None.or_else(|| f("mod", BuiltinFunctionReference(BuiltinFunction::Mod, sl())))
527            .or_else(|| f("round", BuiltinFunctionReference(BuiltinFunction::Round, sl())))
528            .or_else(|| f("ceil", BuiltinFunctionReference(BuiltinFunction::Ceil, sl())))
529            .or_else(|| f("floor", BuiltinFunctionReference(BuiltinFunction::Floor, sl())))
530            .or_else(|| f("abs", BuiltinFunctionReference(BuiltinFunction::Abs, sl())))
531            .or_else(|| f("sqrt", BuiltinFunctionReference(BuiltinFunction::Sqrt, sl())))
532            .or_else(|| f("max", BuiltinMacroReference(BuiltinMacroFunction::Max, t.clone())))
533            .or_else(|| f("min", BuiltinMacroReference(BuiltinMacroFunction::Min, t.clone())))
534            .or_else(|| f("sin", BuiltinFunctionReference(BuiltinFunction::Sin, sl())))
535            .or_else(|| f("cos", BuiltinFunctionReference(BuiltinFunction::Cos, sl())))
536            .or_else(|| f("tan", BuiltinFunctionReference(BuiltinFunction::Tan, sl())))
537            .or_else(|| f("asin", BuiltinFunctionReference(BuiltinFunction::ASin, sl())))
538            .or_else(|| f("acos", BuiltinFunctionReference(BuiltinFunction::ACos, sl())))
539            .or_else(|| f("atan", BuiltinFunctionReference(BuiltinFunction::ATan, sl())))
540            .or_else(|| f("log", BuiltinFunctionReference(BuiltinFunction::Log, sl())))
541            .or_else(|| f("pow", BuiltinFunctionReference(BuiltinFunction::Pow, sl())))
542    }
543}
544
545struct ColorFunctions;
546impl LookupObject for ColorFunctions {
547    fn for_each_entry<R>(
548        &self,
549        ctx: &LookupCtx,
550        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
551    ) -> Option<R> {
552        use Expression::BuiltinMacroReference;
553        let t = &ctx.current_token;
554        let mut f = |n, e: Expression| f(n, e.into());
555        None.or_else(|| f("rgb", BuiltinMacroReference(BuiltinMacroFunction::Rgb, t.clone())))
556            .or_else(|| f("rgba", BuiltinMacroReference(BuiltinMacroFunction::Rgb, t.clone())))
557    }
558}
559
560struct BuiltinFunctionLookup;
561impl LookupObject for BuiltinFunctionLookup {
562    fn for_each_entry<R>(
563        &self,
564        ctx: &LookupCtx,
565        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
566    ) -> Option<R> {
567        (MathFunctions, ColorFunctions).for_each_entry(ctx, f).or_else(|| {
568            f(
569                "debug",
570                Expression::BuiltinMacroReference(
571                    BuiltinMacroFunction::Debug,
572                    ctx.current_token.clone(),
573                )
574                .into(),
575            )
576        })
577    }
578}
579
580struct BuiltinNamespaceLookup;
581impl LookupObject for BuiltinNamespaceLookup {
582    fn for_each_entry<R>(
583        &self,
584        _ctx: &LookupCtx,
585        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
586    ) -> Option<R> {
587        None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors)))
588            .or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math)))
589            .or_else(|| f("Keys", LookupResult::Namespace(BuiltinNamespace::Keys)))
590    }
591}
592
593pub fn global_lookup() -> impl LookupObject {
594    (
595        ArgumentsLookup,
596        (
597            SpecialIdLookup,
598            (
599                IdLookup,
600                (
601                    InScopeLookup,
602                    (
603                        LookupType,
604                        (BuiltinNamespaceLookup, (ReturnTypeSpecificLookup, BuiltinFunctionLookup)),
605                    ),
606                ),
607            ),
608        ),
609    )
610}
611
612impl LookupObject for Expression {
613    fn for_each_entry<R>(
614        &self,
615        ctx: &LookupCtx,
616        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
617    ) -> Option<R> {
618        match self {
619            Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f),
620            _ => match self.ty() {
621                Type::Struct { fields, .. } => {
622                    for name in fields.keys() {
623                        if let Some(r) = f(
624                            name,
625                            Expression::StructFieldAccess {
626                                base: Box::new(self.clone()),
627                                name: name.clone(),
628                            }
629                            .into(),
630                        ) {
631                            return Some(r);
632                        }
633                    }
634                    None
635                }
636                Type::Component(c) => c.root_element.for_each_entry(ctx, f),
637                Type::String => StringExpression(self).for_each_entry(ctx, f),
638                Type::Color => ColorExpression(self).for_each_entry(ctx, f),
639                Type::Image => ImageExpression(self).for_each_entry(ctx, f),
640                Type::Array(_) => ArrayExpression(self).for_each_entry(ctx, f),
641                _ => None,
642            },
643        }
644    }
645
646    fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
647        match self {
648            Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name),
649            _ => match self.ty() {
650                Type::Struct { fields, .. } => fields.contains_key(name).then(|| {
651                    LookupResult::from(Expression::StructFieldAccess {
652                        base: Box::new(self.clone()),
653                        name: name.to_string(),
654                    })
655                }),
656                Type::Component(c) => c.root_element.lookup(ctx, name),
657                Type::String => StringExpression(self).lookup(ctx, name),
658                Type::Color => ColorExpression(self).lookup(ctx, name),
659                Type::Image => ImageExpression(self).lookup(ctx, name),
660                Type::Array(_) => ArrayExpression(self).lookup(ctx, name),
661                _ => None,
662            },
663        }
664    }
665}
666
667struct StringExpression<'a>(&'a Expression);
668impl<'a> LookupObject for StringExpression<'a> {
669    fn for_each_entry<R>(
670        &self,
671        ctx: &LookupCtx,
672        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
673    ) -> Option<R> {
674        let member_function = |f: BuiltinFunction| {
675            LookupResult::from(Expression::MemberFunction {
676                base: Box::new(self.0.clone()),
677                base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
678                member: Box::new(Expression::BuiltinFunctionReference(
679                    f,
680                    ctx.current_token.as_ref().map(|t| t.to_source_location()),
681                )),
682            })
683        };
684        None.or_else(|| f("is-float", member_function(BuiltinFunction::StringIsFloat)))
685            .or_else(|| f("to-float", member_function(BuiltinFunction::StringToFloat)))
686    }
687}
688struct ColorExpression<'a>(&'a Expression);
689impl<'a> LookupObject for ColorExpression<'a> {
690    fn for_each_entry<R>(
691        &self,
692        ctx: &LookupCtx,
693        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
694    ) -> Option<R> {
695        let member_function = |f: BuiltinFunction| {
696            LookupResult::from(Expression::MemberFunction {
697                base: Box::new(self.0.clone()),
698                base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
699                member: Box::new(Expression::BuiltinFunctionReference(
700                    f,
701                    ctx.current_token.as_ref().map(|t| t.to_source_location()),
702                )),
703            })
704        };
705        None.or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
706            .or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
707    }
708}
709
710struct ImageExpression<'a>(&'a Expression);
711impl<'a> LookupObject for ImageExpression<'a> {
712    fn for_each_entry<R>(
713        &self,
714        ctx: &LookupCtx,
715        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
716    ) -> Option<R> {
717        let field_access = |f: &str| {
718            LookupResult::from(Expression::StructFieldAccess {
719                base: Box::new(Expression::FunctionCall {
720                    function: Box::new(Expression::BuiltinFunctionReference(
721                        BuiltinFunction::ImageSize,
722                        ctx.current_token.as_ref().map(|t| t.to_source_location()),
723                    )),
724                    source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
725                    arguments: vec![self.0.clone()],
726                }),
727                name: f.into(),
728            })
729        };
730        None.or_else(|| f("width", field_access("width")))
731            .or_else(|| f("height", field_access("height")))
732    }
733}
734
735struct ArrayExpression<'a>(&'a Expression);
736impl<'a> LookupObject for ArrayExpression<'a> {
737    fn for_each_entry<R>(
738        &self,
739        ctx: &LookupCtx,
740        f: &mut impl FnMut(&str, LookupResult) -> Option<R>,
741    ) -> Option<R> {
742        let member_function = |f: BuiltinFunction| {
743            LookupResult::from(Expression::FunctionCall {
744                function: Box::new(Expression::BuiltinFunctionReference(
745                    f,
746                    ctx.current_token.as_ref().map(|t| t.to_source_location()),
747                )),
748                source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
749                arguments: vec![self.0.clone()],
750            })
751        };
752        None.or_else(|| f("length", member_function(BuiltinFunction::ArrayLength)))
753    }
754}