// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial //! Helper to do lookup in expressions use std::rc::Rc; use crate::diagnostics::{BuildDiagnostics, Spanned}; use crate::expression_tree::{ BuiltinFunction, BuiltinMacroFunction, EasingCurve, Expression, Unit, }; use crate::langtype::{ElementType, Enumeration, EnumerationValue, Type}; use crate::namedreference::NamedReference; use crate::object_tree::{ElementRc, PropertyVisibility}; use crate::parser::NodeOrToken; use crate::typeregister::TypeRegister; use std::cell::RefCell; /// Contains information which allow to lookup identifier in expressions pub struct LookupCtx<'a> { /// the name of the property for which this expression refers. pub property_name: Option<&'a str>, /// the type of the property for which this expression refers. /// (some property come in the scope) pub property_type: Type, /// Here is the stack in which id applies. (the last element in the scope is looked up first) pub component_scope: &'a [ElementRc], /// Somewhere to report diagnostics pub diag: &'a mut BuildDiagnostics, /// The name of the arguments of the callback or function pub arguments: Vec, /// The type register in which to look for Globals pub type_register: &'a TypeRegister, /// The type loader instance, which may be used to resolve relative path references /// for example for img! pub type_loader: Option<&'a crate::typeloader::TypeLoader>, /// The token currently processed pub current_token: Option, } impl<'a> LookupCtx<'a> { /// Return a context that is just suitable to build simple const expression pub fn empty_context(type_register: &'a TypeRegister, diag: &'a mut BuildDiagnostics) -> Self { Self { property_name: Default::default(), property_type: Default::default(), component_scope: Default::default(), diag, arguments: Default::default(), type_register, type_loader: None, current_token: None, } } pub fn return_type(&self) -> &Type { match &self.property_type { Type::Callback { return_type, .. } => { return_type.as_ref().map_or(&Type::Void, |b| &(**b)) } Type::Function { return_type, .. } => &return_type, _ => &self.property_type, } } pub fn is_legacy_component(&self) -> bool { self.component_scope.first().map_or(false, |e| e.borrow().is_legacy_syntax) } /// True if the element is in the same component as the scope pub fn is_local_element(&self, elem: &ElementRc) -> bool { Option::zip( elem.borrow().enclosing_component.upgrade(), self.component_scope.first().and_then(|x| x.borrow().enclosing_component.upgrade()), ) .map_or(true, |(x, y)| Rc::ptr_eq(&x, &y)) } } pub enum LookupResult { Expression { expression: Expression, /// When set, this is deprecated, and the string is the new name deprecated: Option, }, Enumeration(Rc), Namespace(BuiltinNamespace), } pub enum BuiltinNamespace { Colors, Math, Key, SlintInternal, } impl From for LookupResult { fn from(expression: Expression) -> Self { Self::Expression { expression, deprecated: None } } } impl LookupResult { pub fn deprecated(&self) -> Option<&str> { match self { Self::Expression { deprecated: Some(x), .. } => Some(x.as_str()), _ => None, } } } /// Represent an object which has properties which can be accessible pub trait LookupObject { /// Will call the function for each entry (useful for completion) /// If the function return Some, it will immediately be returned and not called further fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option; /// Perform a lookup of a given identifier. /// One does not have to re-implement unless we can make it faster fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option { self.for_each_entry(ctx, &mut |prop, expr| (prop == name).then(|| expr)) } } impl LookupObject for (T1, T2) { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { self.0.for_each_entry(ctx, f).or_else(|| self.1.for_each_entry(ctx, f)) } fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option { self.0.lookup(ctx, name).or_else(|| self.1.lookup(ctx, name)) } } impl LookupObject for LookupResult { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { match self { LookupResult::Expression { expression, .. } => expression.for_each_entry(ctx, f), LookupResult::Enumeration(e) => e.for_each_entry(ctx, f), LookupResult::Namespace(BuiltinNamespace::Colors) => { (ColorSpecific, ColorFunctions).for_each_entry(ctx, f) } LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.for_each_entry(ctx, f), LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.for_each_entry(ctx, f), LookupResult::Namespace(BuiltinNamespace::SlintInternal) => { SlintInternal.for_each_entry(ctx, f) } } } fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option { match self { LookupResult::Expression { expression, .. } => expression.lookup(ctx, name), LookupResult::Enumeration(e) => e.lookup(ctx, name), LookupResult::Namespace(BuiltinNamespace::Colors) => { (ColorSpecific, ColorFunctions).lookup(ctx, name) } LookupResult::Namespace(BuiltinNamespace::Math) => MathFunctions.lookup(ctx, name), LookupResult::Namespace(BuiltinNamespace::Key) => KeysLookup.lookup(ctx, name), LookupResult::Namespace(BuiltinNamespace::SlintInternal) => { SlintInternal.lookup(ctx, name) } } } } struct ArgumentsLookup; impl LookupObject for ArgumentsLookup { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { let args = match &ctx.property_type { Type::Callback { args, .. } | Type::Function { args, .. } => args, _ => return None, }; for (index, (name, ty)) in ctx.arguments.iter().zip(args.iter()).enumerate() { if let Some(r) = f(name, Expression::FunctionParameterReference { index, ty: ty.clone() }.into()) { return Some(r); } } None } } struct SpecialIdLookup; impl LookupObject for SpecialIdLookup { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { let last = ctx.component_scope.last(); None.or_else(|| f("self", Expression::ElementReference(Rc::downgrade(last?)).into())) .or_else(|| { let len = ctx.component_scope.len(); if len >= 2 { f( "parent", Expression::ElementReference(Rc::downgrade(&ctx.component_scope[len - 2])) .into(), ) } else { None } }) .or_else(|| f("true", Expression::BoolLiteral(true).into())) .or_else(|| f("false", Expression::BoolLiteral(false).into())) // "root" is just a normal id } } struct IdLookup; impl LookupObject for IdLookup { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { fn visit( root: &ElementRc, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { if !root.borrow().id.is_empty() { if let Some(r) = f(&root.borrow().id, Expression::ElementReference(Rc::downgrade(root)).into()) { return Some(r); } } for x in &root.borrow().children { if x.borrow().repeated.is_some() { continue; } if let Some(r) = visit(&x, f) { return Some(r); } } None } for e in ctx.component_scope.iter().rev() { if e.borrow().repeated.is_some() { if let Some(r) = visit(e, f) { return Some(r); } } } if let Some(root) = ctx.component_scope.first() { if let Some(r) = visit(root, f) { return Some(r); } } None } // TODO: hash based lookup } struct InScopeLookup; impl InScopeLookup { fn visit_scope( ctx: &LookupCtx, mut visit_entry: impl FnMut(&str, LookupResult) -> Option, mut visit_legacy_scope: impl FnMut(&ElementRc) -> Option, mut visit_scope: impl FnMut(&ElementRc) -> Option, ) -> Option { let is_legacy = ctx.is_legacy_component(); for (idx, elem) in ctx.component_scope.iter().rev().enumerate() { if let Some(repeated) = &elem.borrow().repeated { if !repeated.index_id.is_empty() { if let Some(r) = visit_entry( &repeated.index_id, Expression::RepeaterIndexReference { element: Rc::downgrade(elem) }.into(), ) { return Some(r); } } if !repeated.model_data_id.is_empty() { if let Some(r) = visit_entry( &repeated.model_data_id, Expression::RepeaterModelReference { element: Rc::downgrade(elem) }.into(), ) { return Some(r); } } } if is_legacy { if elem.borrow().repeated.is_some() || idx == 0 || idx == ctx.component_scope.len() - 1 { if let Some(r) = visit_legacy_scope(elem) { return Some(r); } } } else { if let Some(r) = visit_scope(elem) { return Some(r); } } } None } } impl LookupObject for InScopeLookup { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { let f = RefCell::new(f); Self::visit_scope( ctx, |str, r| f.borrow_mut()(str, r), |elem| elem.for_each_entry(ctx, *f.borrow_mut()), |elem| { for (name, prop) in &elem.borrow().property_declarations { let e = expression_from_reference( NamedReference::new(elem, name), &prop.property_type, &ctx.current_token, ); if let Some(r) = f.borrow_mut()(name, e.into()) { return Some(r); } } None }, ) } fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option { if name.is_empty() { return None; } Self::visit_scope( ctx, |str, r| (str == name).then(|| r), |elem| elem.lookup(ctx, name), |elem| { elem.borrow().property_declarations.get(name).map(|prop| { expression_from_reference( NamedReference::new(elem, name), &prop.property_type, &ctx.current_token, ) .into() }) }, ) } } impl LookupObject for ElementRc { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { for (name, prop) in &self.borrow().property_declarations { let e = expression_from_reference( NamedReference::new(self, name), &prop.property_type, &ctx.current_token, ); if let Some(r) = f(name, e.into()) { return Some(r); } } let list = self.borrow().base_type.property_list(); for (name, ty) in list { let e = expression_from_reference( NamedReference::new(self, &name), &ty, &ctx.current_token, ); if let Some(r) = f(&name, e.into()) { return Some(r); } } for (name, ty) in crate::typeregister::reserved_properties() { let e = expression_from_reference(NamedReference::new(self, name), &ty, &ctx.current_token); if let Some(r) = f(name, e.into()) { return Some(r); } } None } fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option { let lookup_result = self.borrow().lookup_property(name); if lookup_result.property_type != Type::Invalid && (lookup_result.is_local_to_component || lookup_result.property_visibility != PropertyVisibility::Private) { Some(LookupResult::Expression { expression: expression_from_reference( NamedReference::new(self, &lookup_result.resolved_name), &lookup_result.property_type, &ctx.current_token, ), deprecated: (lookup_result.resolved_name != name) .then(|| lookup_result.resolved_name.to_string()), }) } else { None } } } fn expression_from_reference( n: NamedReference, ty: &Type, node: &Option, ) -> Expression { match ty { Type::Callback { .. } => Expression::CallbackReference(n, node.clone()), Type::InferredCallback => Expression::CallbackReference(n, node.clone()), Type::Function { .. } => Expression::FunctionReference(n, node.clone()), _ => Expression::PropertyReference(n), } } /// Lookup for Globals and Enum. /// Note: for enums, the expression's value is `usize::MAX` struct LookupType; impl LookupObject for LookupType { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { for (name, ty) in ctx.type_register.all_types() { if let Some(r) = Self::from_type(ty).and_then(|e| f(&name, e)) { return Some(r); } } for (name, ty) in ctx.type_register.all_elements() { if let Some(r) = Self::from_element(ty).and_then(|e| f(&name, e)) { return Some(r); } } None } fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option { Self::from_type(ctx.type_register.lookup(name)) .or_else(|| Self::from_element(ctx.type_register.lookup_element(name).ok()?)) } } impl LookupType { fn from_type(ty: Type) -> Option { match ty { Type::Enumeration(e) => Some(LookupResult::Enumeration(e)), _ => None, } } fn from_element(el: ElementType) -> Option { match el { ElementType::Component(c) if c.is_global() => { Some(Expression::ElementReference(Rc::downgrade(&c.root_element)).into()) } _ => None, } } } struct ReturnTypeSpecificLookup; impl LookupObject for ReturnTypeSpecificLookup { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { match ctx.return_type() { Type::Color => ColorSpecific.for_each_entry(ctx, f), Type::Brush => ColorSpecific.for_each_entry(ctx, f), Type::Easing => EasingSpecific.for_each_entry(ctx, f), Type::Enumeration(enumeration) => enumeration.clone().for_each_entry(ctx, f), _ => None, } } fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option { match ctx.return_type() { Type::Color => ColorSpecific.lookup(ctx, name), Type::Brush => ColorSpecific.lookup(ctx, name), Type::Easing => EasingSpecific.lookup(ctx, name), Type::Enumeration(enumeration) => enumeration.clone().lookup(ctx, name), _ => None, } } } struct ColorSpecific; impl LookupObject for ColorSpecific { fn for_each_entry( &self, _ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { for (name, c) in css_color_parser2::NAMED_COLORS.iter() { if let Some(r) = f(name, Self::as_result(*c)) { return Some(r); } } None } fn lookup(&self, _ctx: &LookupCtx, name: &str) -> Option { css_color_parser2::NAMED_COLORS.get(name).map(|c| Self::as_result(*c)) } } impl ColorSpecific { fn as_result(c: css_color_parser2::Color) -> LookupResult { let value = ((c.a as u32 * 255) << 24) | ((c.r as u32) << 16) | ((c.g as u32) << 8) | (c.b as u32); Expression::Cast { from: Box::new(Expression::NumberLiteral(value as f64, Unit::None)), to: Type::Color, } .into() } } struct KeysLookup; macro_rules! special_keys_lookup { ($($char:literal # $name:ident # $($qt:ident)|* # $($winit:ident)|* ;)*) => { impl LookupObject for KeysLookup { fn for_each_entry( &self, _ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { None $(.or_else(|| { f(stringify!($name), Expression::StringLiteral($char.into()).into()) }))* } } }; } i_slint_common::for_each_special_keys!(special_keys_lookup); struct EasingSpecific; impl LookupObject for EasingSpecific { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { use EasingCurve::CubicBezier; None.or_else(|| f("linear", Expression::EasingCurve(EasingCurve::Linear).into())) .or_else(|| { f("ease", Expression::EasingCurve(CubicBezier(0.25, 0.1, 0.25, 1.0)).into()) }) .or_else(|| { f("ease-in", Expression::EasingCurve(CubicBezier(0.42, 0.0, 1.0, 1.0)).into()) }) .or_else(|| { f("ease-in-out", Expression::EasingCurve(CubicBezier(0.42, 0.0, 0.58, 1.0)).into()) }) .or_else(|| { f("ease-out", Expression::EasingCurve(CubicBezier(0.0, 0.0, 0.58, 1.0)).into()) }) .or_else(|| { f( "cubic-bezier", Expression::BuiltinMacroReference( BuiltinMacroFunction::CubicBezier, ctx.current_token.clone(), ) .into(), ) }) } } impl LookupObject for Rc { fn for_each_entry( &self, _ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { for (value, name) in self.values.iter().enumerate() { if let Some(r) = f( name, Expression::EnumerationValue(EnumerationValue { value, enumeration: self.clone() }) .into(), ) { return Some(r); } } None } } struct MathFunctions; impl LookupObject for MathFunctions { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { use Expression::{BuiltinFunctionReference, BuiltinMacroReference}; let t = &ctx.current_token; let sl = || t.as_ref().map(|t| t.to_source_location()); let mut f = |n, e: Expression| f(n, e.into()); None.or_else(|| f("mod", BuiltinMacroReference(BuiltinMacroFunction::Mod, t.clone()))) .or_else(|| f("round", BuiltinFunctionReference(BuiltinFunction::Round, sl()))) .or_else(|| f("ceil", BuiltinFunctionReference(BuiltinFunction::Ceil, sl()))) .or_else(|| f("floor", BuiltinFunctionReference(BuiltinFunction::Floor, sl()))) .or_else(|| f("abs", BuiltinFunctionReference(BuiltinFunction::Abs, sl()))) .or_else(|| f("sqrt", BuiltinFunctionReference(BuiltinFunction::Sqrt, sl()))) .or_else(|| f("max", BuiltinMacroReference(BuiltinMacroFunction::Max, t.clone()))) .or_else(|| f("min", BuiltinMacroReference(BuiltinMacroFunction::Min, t.clone()))) .or_else(|| f("sin", BuiltinFunctionReference(BuiltinFunction::Sin, sl()))) .or_else(|| f("cos", BuiltinFunctionReference(BuiltinFunction::Cos, sl()))) .or_else(|| f("tan", BuiltinFunctionReference(BuiltinFunction::Tan, sl()))) .or_else(|| f("asin", BuiltinFunctionReference(BuiltinFunction::ASin, sl()))) .or_else(|| f("acos", BuiltinFunctionReference(BuiltinFunction::ACos, sl()))) .or_else(|| f("atan", BuiltinFunctionReference(BuiltinFunction::ATan, sl()))) .or_else(|| f("log", BuiltinFunctionReference(BuiltinFunction::Log, sl()))) .or_else(|| f("pow", BuiltinFunctionReference(BuiltinFunction::Pow, sl()))) } } struct SlintInternal; impl LookupObject for SlintInternal { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { f( "dark-color-scheme", Expression::FunctionCall { function: Expression::BuiltinFunctionReference( BuiltinFunction::DarkColorScheme, None, ) .into(), arguments: vec![], source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()), } .into(), ) } } struct ColorFunctions; impl LookupObject for ColorFunctions { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { use Expression::BuiltinMacroReference; let t = &ctx.current_token; let mut f = |n, e: Expression| f(n, e.into()); None.or_else(|| f("rgb", BuiltinMacroReference(BuiltinMacroFunction::Rgb, t.clone()))) .or_else(|| f("rgba", BuiltinMacroReference(BuiltinMacroFunction::Rgb, t.clone()))) } } struct BuiltinFunctionLookup; impl LookupObject for BuiltinFunctionLookup { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { (MathFunctions, ColorFunctions) .for_each_entry(ctx, f) .or_else(|| { f( "debug", Expression::BuiltinMacroReference( BuiltinMacroFunction::Debug, ctx.current_token.clone(), ) .into(), ) }) .or_else(|| { f( "animation-tick", Expression::BuiltinFunctionReference( BuiltinFunction::AnimationTick, ctx.current_token.as_ref().map(|t| t.to_source_location()), ) .into(), ) }) } } struct BuiltinNamespaceLookup; impl LookupObject for BuiltinNamespaceLookup { fn for_each_entry( &self, _ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { None.or_else(|| f("Colors", LookupResult::Namespace(BuiltinNamespace::Colors))) .or_else(|| f("Math", LookupResult::Namespace(BuiltinNamespace::Math))) .or_else(|| f("Key", LookupResult::Namespace(BuiltinNamespace::Key))) .or_else(|| { f("SlintInternal", LookupResult::Namespace(BuiltinNamespace::SlintInternal)) }) } } pub fn global_lookup() -> impl LookupObject { ( ArgumentsLookup, ( SpecialIdLookup, ( IdLookup, ( InScopeLookup, ( LookupType, (BuiltinNamespaceLookup, (ReturnTypeSpecificLookup, BuiltinFunctionLookup)), ), ), ), ), ) } impl LookupObject for Expression { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { match self { Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f), _ => match self.ty() { Type::Struct { fields, .. } => { for name in fields.keys() { if let Some(r) = f( name, Expression::StructFieldAccess { base: Box::new(self.clone()), name: name.clone(), } .into(), ) { return Some(r); } } None } Type::String => StringExpression(self).for_each_entry(ctx, f), Type::Brush | Type::Color => ColorExpression(self).for_each_entry(ctx, f), Type::Image => ImageExpression(self).for_each_entry(ctx, f), Type::Array(_) => ArrayExpression(self).for_each_entry(ctx, f), _ => None, }, } } fn lookup(&self, ctx: &LookupCtx, name: &str) -> Option { match self { Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name), _ => match self.ty() { Type::Struct { fields, .. } => fields.contains_key(name).then(|| { LookupResult::from(Expression::StructFieldAccess { base: Box::new(self.clone()), name: name.to_string(), }) }), Type::String => StringExpression(self).lookup(ctx, name), Type::Brush | Type::Color => ColorExpression(self).lookup(ctx, name), Type::Image => ImageExpression(self).lookup(ctx, name), Type::Array(_) => ArrayExpression(self).lookup(ctx, name), _ => None, }, } } } struct StringExpression<'a>(&'a Expression); impl<'a> LookupObject for StringExpression<'a> { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { let member_function = |f: BuiltinFunction| { LookupResult::from(Expression::MemberFunction { base: Box::new(self.0.clone()), base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node member: Box::new(Expression::BuiltinFunctionReference( f, ctx.current_token.as_ref().map(|t| t.to_source_location()), )), }) }; None.or_else(|| f("is-float", member_function(BuiltinFunction::StringIsFloat))) .or_else(|| f("to-float", member_function(BuiltinFunction::StringToFloat))) } } struct ColorExpression<'a>(&'a Expression); impl<'a> LookupObject for ColorExpression<'a> { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { let member_function = |f: BuiltinFunction| { LookupResult::from(Expression::MemberFunction { base: Box::new(self.0.clone()), base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node member: Box::new(Expression::BuiltinFunctionReference( f, ctx.current_token.as_ref().map(|t| t.to_source_location()), )), }) }; None.or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter))) .or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker))) } } struct ImageExpression<'a>(&'a Expression); impl<'a> LookupObject for ImageExpression<'a> { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { let field_access = |f: &str| { LookupResult::from(Expression::StructFieldAccess { base: Box::new(Expression::FunctionCall { function: Box::new(Expression::BuiltinFunctionReference( BuiltinFunction::ImageSize, ctx.current_token.as_ref().map(|t| t.to_source_location()), )), source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()), arguments: vec![self.0.clone()], }), name: f.into(), }) }; None.or_else(|| f("width", field_access("width"))) .or_else(|| f("height", field_access("height"))) } } struct ArrayExpression<'a>(&'a Expression); impl<'a> LookupObject for ArrayExpression<'a> { fn for_each_entry( &self, ctx: &LookupCtx, f: &mut impl FnMut(&str, LookupResult) -> Option, ) -> Option { let member_function = |f: BuiltinFunction| { LookupResult::from(Expression::FunctionCall { function: Box::new(Expression::BuiltinFunctionReference( f, ctx.current_token.as_ref().map(|t| t.to_source_location()), )), source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()), arguments: vec![self.0.clone()], }) }; None.or_else(|| f("length", member_function(BuiltinFunction::ArrayLength))) } }