slint/internal/compiler/lookup.rs
Tobias Hunger 447ee5de44 live-preview: Handle colors/brushes using palette names
Find color names/values defined in palettes in property bindings.

Make sure to not report those as code.

Do a lot more complex evaluation of color values "behind" the palette name
to get a useful value out for the palete name. This will e.g. follow the
true branch of conditions, handle Structs, struct field access and references to
globals when looking for the color value.
2025-05-21 16:28:03 +02:00

1108 lines
42 KiB
Rust

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
//! Helper to do lookup in expressions
use std::rc::Rc;
use crate::diagnostics::{BuildDiagnostics, Spanned};
use crate::expression_tree::{
BuiltinFunction, BuiltinMacroFunction, Callable, 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 smol_str::{SmolStr, ToSmolStr};
use std::cell::RefCell;
mod named_colors;
pub use named_colors::named_colors;
/// 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<SmolStr>,
/// 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<NodeOrToken>,
}
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(f) | Type::Function(f) => &f.return_type,
_ => &self.property_type,
}
}
pub fn is_legacy_component(&self) -> bool {
self.component_scope.first().is_some_and(|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))
}
}
#[derive(Debug)]
pub enum LookupResult {
Expression {
expression: Expression,
/// When set, this is deprecated, and the string is the new name
deprecated: Option<String>,
},
Enumeration(Rc<Enumeration>),
Namespace(BuiltinNamespace),
Callable(LookupResultCallable),
}
#[derive(Debug)]
pub enum LookupResultCallable {
Callable(Callable),
Macro(BuiltinMacroFunction),
/// for example for `item.focus`, where `item` is the base
MemberFunction {
/// This becomes the first argument of the function call
base: Expression,
base_node: Option<NodeOrToken>,
member: Box<LookupResultCallable>,
},
}
#[derive(Debug, derive_more::Display)]
pub enum BuiltinNamespace {
Colors,
Math,
Key,
SlintInternal,
}
impl From<Expression> for LookupResult {
fn from(expression: Expression) -> Self {
Self::Expression { expression, deprecated: None }
}
}
impl From<Callable> for LookupResult {
fn from(callable: Callable) -> Self {
Self::Callable(LookupResultCallable::Callable(callable))
}
}
impl From<BuiltinMacroFunction> for LookupResult {
fn from(macro_function: BuiltinMacroFunction) -> Self {
Self::Callable(LookupResultCallable::Macro(macro_function))
}
}
impl From<BuiltinFunction> for LookupResult {
fn from(function: BuiltinFunction) -> Self {
Self::Callable(LookupResultCallable::Callable(Callable::Builtin(function)))
}
}
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<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R>;
/// 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: &SmolStr) -> Option<LookupResult> {
self.for_each_entry(ctx, &mut |prop, expr| (prop == name).then_some(expr))
}
}
impl<T1: LookupObject, T2: LookupObject> LookupObject for (T1, T2) {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
self.0.for_each_entry(ctx, f).or_else(|| self.1.for_each_entry(ctx, f))
}
fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
self.0.lookup(ctx, name).or_else(|| self.1.lookup(ctx, name))
}
}
impl LookupObject for LookupResult {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
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)
}
LookupResult::Callable(..) => None,
}
}
fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
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)
}
LookupResult::Callable(..) => None,
}
}
}
struct ArgumentsLookup;
impl LookupObject for ArgumentsLookup {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let args = match &ctx.property_type {
Type::Callback(f) | Type::Function(f) => &f.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<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let last = ctx.component_scope.last();
let mut f = |n, e: Expression| f(&SmolStr::new_static(n), e.into());
None.or_else(|| f("self", Expression::ElementReference(Rc::downgrade(last?))))
.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)))
.or_else(|| f("false", Expression::BoolLiteral(false)))
// "root" is just a normal id
}
}
struct IdLookup;
impl LookupObject for IdLookup {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
fn visit<R>(
root: &ElementRc,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
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
}
/// In-scope properties, or model
pub struct InScopeLookup;
impl InScopeLookup {
fn visit_scope<R>(
ctx: &LookupCtx,
mut visit_entry: impl FnMut(&SmolStr, LookupResult) -> Option<R>,
mut visit_legacy_scope: impl FnMut(&ElementRc) -> Option<R>,
mut visit_scope: impl FnMut(&ElementRc) -> Option<R>,
) -> Option<R> {
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<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
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.clone()),
&prop.property_type,
None,
);
if let Some(r) = f.borrow_mut()(name, e) {
return Some(r);
}
}
None
},
)
}
fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
if name.is_empty() {
return None;
}
Self::visit_scope(
ctx,
|str, r| (str == name).then_some(r),
|elem| elem.lookup(ctx, name),
|elem| {
elem.borrow().property_declarations.get(name).map(|prop| {
expression_from_reference(
NamedReference::new(elem, name.clone()),
&prop.property_type,
None,
)
})
},
)
}
}
impl LookupObject for ElementRc {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
for (name, prop) in &self.borrow().property_declarations {
let r = expression_from_reference(
NamedReference::new(self, name.clone()),
&prop.property_type,
check_deprecated_stylemetrics(self, ctx, name),
);
if let Some(r) = f(name, r) {
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.clone()), &ty, None);
if let Some(r) = f(&name, e) {
return Some(r);
}
}
if !(matches!(self.borrow().base_type, ElementType::Global)) {
for (name, ty, _) in crate::typeregister::reserved_properties() {
let name = SmolStr::new_static(name);
let e =
expression_from_reference(NamedReference::new(self, name.clone()), &ty, None);
if let Some(r) = f(&name, e) {
return Some(r);
}
}
}
None
}
fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
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)
{
let deprecated = (lookup_result.resolved_name != name.as_str())
.then(|| lookup_result.resolved_name.to_string())
.or_else(|| check_deprecated_stylemetrics(self, ctx, name));
Some(expression_from_reference(
NamedReference::new(self, lookup_result.resolved_name.to_smolstr()),
&lookup_result.property_type,
deprecated,
))
} else {
None
}
}
}
pub fn check_deprecated_stylemetrics(
elem: &ElementRc,
ctx: &LookupCtx<'_>,
name: &SmolStr,
) -> Option<String> {
let borrow = elem.borrow();
(!ctx.type_register.expose_internal_types
&& matches!(
borrow.enclosing_component.upgrade().unwrap().id.as_str(),
"StyleMetrics" | "NativeStyleMetrics"
)
&& borrow
.debug
.first()
.and_then(|x| x.node.source_file())
.map_or(true, |x| x.path().starts_with("builtin:"))
&& !name.starts_with("layout-"))
.then(|| format!("Palette.{name}"))
}
fn expression_from_reference(
n: NamedReference,
ty: &Type,
deprecated: Option<String>,
) -> LookupResult {
match ty {
Type::Callback { .. } => Callable::Callback(n).into(),
Type::InferredCallback => Callable::Callback(n).into(),
Type::Function { .. } => Callable::Function(n).into(),
_ => LookupResult::Expression { expression: Expression::PropertyReference(n), deprecated },
}
}
/// Lookup for Globals and Enum.
struct LookupType;
impl LookupObject for LookupType {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
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, ctx, &name).and_then(|e| f(&name, e)) {
return Some(r);
}
}
None
}
fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
Self::from_type(ctx.type_register.lookup(name))
.or_else(|| Self::from_element(ctx.type_register.lookup_element(name).ok()?, ctx, name))
}
}
impl LookupType {
fn from_type(ty: Type) -> Option<LookupResult> {
match ty {
Type::Enumeration(e) => Some(LookupResult::Enumeration(e)),
_ => None,
}
}
fn from_element(el: ElementType, ctx: &LookupCtx, name: &str) -> Option<LookupResult> {
match el {
ElementType::Component(c) if c.is_global() => {
// Check if it is internal, but allow re-export (different name) eg: NativeStyleMetrics re-exported as StyleMetrics
if c.root_element
.borrow()
.builtin_type()
.is_some_and(|x| x.is_internal && x.name == name)
&& !ctx.type_register.expose_internal_types
{
None
} else {
Some(Expression::ElementReference(Rc::downgrade(&c.root_element)).into())
}
}
_ => None,
}
}
}
/// Lookup for things specific to the return type (eg: colors or enums)
pub struct ReturnTypeSpecificLookup;
impl LookupObject for ReturnTypeSpecificLookup {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
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: &SmolStr) -> Option<LookupResult> {
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<R>(
&self,
_ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
for (name, c) in named_colors::named_colors().iter() {
if let Some(r) = f(&SmolStr::new_static(name), Self::as_result(*c)) {
return Some(r);
}
}
None
}
fn lookup(&self, _ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
named_colors::named_colors().get(name.as_str()).map(|c| Self::as_result(*c))
}
}
impl ColorSpecific {
fn as_result(value: u32) -> LookupResult {
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 $(($_pos:ident))?)|* # $($_xkb:ident)|*;)*) => {
impl LookupObject for KeysLookup {
fn for_each_entry<R>(
&self,
_ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
None
$(.or_else(|| {
let mut tmp = [0; 4];
f(&SmolStr::new_static(stringify!($name)), Expression::StringLiteral(SmolStr::new_inline($char.encode_utf8(&mut tmp))).into())
}))*
}
}
};
}
i_slint_common::for_each_special_keys!(special_keys_lookup);
struct EasingSpecific;
impl LookupObject for EasingSpecific {
fn for_each_entry<R>(
&self,
_ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
use EasingCurve::CubicBezier;
let mut curve = |n, e| f(&SmolStr::new_static(n), Expression::EasingCurve(e).into());
let r = None
.or_else(|| curve("linear", EasingCurve::Linear))
.or_else(|| curve("ease-in-quad", CubicBezier(0.11, 0.0, 0.5, 0.0)))
.or_else(|| curve("ease-out-quad", CubicBezier(0.5, 1.0, 0.89, 1.0)))
.or_else(|| curve("ease-in-out-quad", CubicBezier(0.45, 0.0, 0.55, 1.0)))
.or_else(|| curve("ease", CubicBezier(0.25, 0.1, 0.25, 1.0)))
.or_else(|| curve("ease-in", CubicBezier(0.42, 0.0, 1.0, 1.0)))
.or_else(|| curve("ease-in-out", CubicBezier(0.42, 0.0, 0.58, 1.0)))
.or_else(|| curve("ease-out", CubicBezier(0.0, 0.0, 0.58, 1.0)))
.or_else(|| curve("ease-in-quart", CubicBezier(0.5, 0.0, 0.75, 0.0)))
.or_else(|| curve("ease-out-quart", CubicBezier(0.25, 1.0, 0.5, 1.0)))
.or_else(|| curve("ease-in-out-quart", CubicBezier(0.76, 0.0, 0.24, 1.0)))
.or_else(|| curve("ease-in-quint", CubicBezier(0.64, 0.0, 0.78, 0.0)))
.or_else(|| curve("ease-out-quint", CubicBezier(0.22, 1.0, 0.36, 1.0)))
.or_else(|| curve("ease-in-out-quint", CubicBezier(0.83, 0.0, 0.17, 1.0)))
.or_else(|| curve("ease-in-expo", CubicBezier(0.7, 0.0, 0.84, 0.0)))
.or_else(|| curve("ease-out-expo", CubicBezier(0.16, 1.0, 0.3, 1.0)))
.or_else(|| curve("ease-in-out-expo", CubicBezier(0.87, 0.0, 0.13, 1.0)))
.or_else(|| curve("ease-in-back", CubicBezier(0.36, 0.0, 0.66, -0.56)))
.or_else(|| curve("ease-out-back", CubicBezier(0.34, 1.56, 0.64, 1.0)))
.or_else(|| curve("ease-in-out-back", CubicBezier(0.68, -0.6, 0.32, 1.6)))
.or_else(|| curve("ease-in-sine", CubicBezier(0.12, 0.0, 0.39, 0.0)))
.or_else(|| curve("ease-out-sine", CubicBezier(0.61, 1.0, 0.88, 1.0)))
.or_else(|| curve("ease-in-out-sine", CubicBezier(0.37, 0.0, 0.63, 1.0)))
.or_else(|| curve("ease-in-circ", CubicBezier(0.55, 0.0, 1.0, 0.45)))
.or_else(|| curve("ease-out-circ", CubicBezier(0.0, 0.55, 0.45, 1.0)))
.or_else(|| curve("ease-in-out-circ", CubicBezier(0.85, 0.0, 0.15, 1.0)))
.or_else(|| curve("ease-in-elastic", EasingCurve::EaseInElastic))
.or_else(|| curve("ease-out-elastic", EasingCurve::EaseOutElastic))
.or_else(|| curve("ease-in-out-elastic", EasingCurve::EaseInOutElastic))
.or_else(|| curve("ease-in-bounce", EasingCurve::EaseInBounce))
.or_else(|| curve("ease-out-bounce", EasingCurve::EaseOutBounce))
.or_else(|| curve("ease-in-out-bounce", EasingCurve::EaseInOutBounce));
r.or_else(|| {
f(&SmolStr::new_static("cubic-bezier"), BuiltinMacroFunction::CubicBezier.into())
})
}
}
impl LookupObject for Rc<Enumeration> {
fn for_each_entry<R>(
&self,
_ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
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<R>(
&self,
_ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let mut f = |n, e| f(&SmolStr::new_static(n), e);
let b = |b| LookupResult::from(Callable::Builtin(b));
None.or_else(|| f("mod", BuiltinMacroFunction::Mod.into()))
.or_else(|| f("round", b(BuiltinFunction::Round)))
.or_else(|| f("ceil", b(BuiltinFunction::Ceil)))
.or_else(|| f("floor", b(BuiltinFunction::Floor)))
.or_else(|| f("clamp", BuiltinMacroFunction::Clamp.into()))
.or_else(|| f("abs", BuiltinMacroFunction::Abs.into()))
.or_else(|| f("sqrt", b(BuiltinFunction::Sqrt)))
.or_else(|| f("max", BuiltinMacroFunction::Max.into()))
.or_else(|| f("min", BuiltinMacroFunction::Min.into()))
.or_else(|| f("sin", b(BuiltinFunction::Sin)))
.or_else(|| f("cos", b(BuiltinFunction::Cos)))
.or_else(|| f("tan", b(BuiltinFunction::Tan)))
.or_else(|| f("asin", b(BuiltinFunction::ASin)))
.or_else(|| f("acos", b(BuiltinFunction::ACos)))
.or_else(|| f("atan", b(BuiltinFunction::ATan)))
.or_else(|| f("atan2", b(BuiltinFunction::ATan2)))
.or_else(|| f("log", b(BuiltinFunction::Log)))
.or_else(|| f("ln", b(BuiltinFunction::Ln)))
.or_else(|| f("pow", b(BuiltinFunction::Pow)))
.or_else(|| f("exp", b(BuiltinFunction::Exp)))
}
}
struct SlintInternal;
impl LookupObject for SlintInternal {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let sl = || ctx.current_token.as_ref().map(|t| t.to_source_location());
let mut f = |n, e: LookupResult| f(&SmolStr::new_static(n), e);
let b = |b| LookupResult::from(Callable::Builtin(b));
None.or_else(|| {
let style = ctx.type_loader.and_then(|tl| tl.compiler_config.style.as_ref());
f(
"color-scheme",
if style.is_some_and(|s| s.ends_with("-light")) {
let e = crate::typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone());
Expression::EnumerationValue(e.try_value_from_string("light").unwrap())
} else if style.is_some_and(|s| s.ends_with("-dark")) {
let e = crate::typeregister::BUILTIN.with(|e| e.enums.ColorScheme.clone());
Expression::EnumerationValue(e.try_value_from_string("dark").unwrap())
} else {
Expression::FunctionCall {
function: BuiltinFunction::ColorScheme.into(),
arguments: vec![],
source_location: sl(),
}
}
.into(),
)
})
.or_else(|| {
f(
"use-24-hour-format",
Expression::FunctionCall {
function: BuiltinFunction::Use24HourFormat.into(),
arguments: vec![],
source_location: sl(),
}
.into(),
)
})
.or_else(|| f("month-day-count", b(BuiltinFunction::MonthDayCount)))
.or_else(|| f("month-offset", b(BuiltinFunction::MonthOffset)))
.or_else(|| f("format-date", b(BuiltinFunction::FormatDate)))
.or_else(|| f("date-now", b(BuiltinFunction::DateNow)))
.or_else(|| f("valid-date", b(BuiltinFunction::ValidDate)))
.or_else(|| f("parse-date", b(BuiltinFunction::ParseDate)))
}
}
struct ColorFunctions;
impl LookupObject for ColorFunctions {
fn for_each_entry<R>(
&self,
_ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let mut f = |n, m| f(&SmolStr::new_static(n), LookupResult::from(m));
None.or_else(|| f("rgb", BuiltinMacroFunction::Rgb))
.or_else(|| f("rgba", BuiltinMacroFunction::Rgb))
.or_else(|| f("hsv", BuiltinMacroFunction::Hsv))
}
}
struct BuiltinFunctionLookup;
impl LookupObject for BuiltinFunctionLookup {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
(MathFunctions, ColorFunctions)
.for_each_entry(ctx, f)
.or_else(|| f(&SmolStr::new_static("debug"), BuiltinMacroFunction::Debug.into()))
.or_else(|| {
f(&SmolStr::new_static("animation-tick"), BuiltinFunction::AnimationTick.into())
})
}
}
struct BuiltinNamespaceLookup;
impl LookupObject for BuiltinNamespaceLookup {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let mut f = |s, res| f(&SmolStr::new_static(s), res);
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(|| {
if ctx.type_register.expose_internal_types {
f("SlintInternal", LookupResult::Namespace(BuiltinNamespace::SlintInternal))
} else {
None
}
})
}
}
pub fn global_lookup() -> impl LookupObject {
(
ArgumentsLookup,
(
SpecialIdLookup,
(
IdLookup,
(
InScopeLookup,
(
LookupType,
(BuiltinNamespaceLookup, (ReturnTypeSpecificLookup, BuiltinFunctionLookup)),
),
),
),
),
)
}
impl LookupObject for Expression {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
match self {
Expression::ElementReference(e) => e.upgrade().unwrap().for_each_entry(ctx, f),
_ => match self.ty() {
Type::Struct(s) => {
for name in s.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),
Type::Float32 | Type::Int32 | Type::Percent => {
NumberExpression(self).for_each_entry(ctx, f)
}
ty if ty.as_unit_product().is_some() => {
NumberWithUnitExpression(self).for_each_entry(ctx, f)
}
_ => None,
},
}
}
fn lookup(&self, ctx: &LookupCtx, name: &SmolStr) -> Option<LookupResult> {
match self {
Expression::ElementReference(e) => e.upgrade().unwrap().lookup(ctx, name),
_ => match self.ty() {
Type::Struct(s) => s.fields.contains_key(name).then(|| {
LookupResult::from(Expression::StructFieldAccess {
base: Box::new(self.clone()),
name: name.clone(),
})
}),
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),
Type::Float32 | Type::Int32 | Type::Percent => {
NumberExpression(self).lookup(ctx, name)
}
ty if ty.as_unit_product().is_some() => {
NumberWithUnitExpression(self).lookup(ctx, name)
}
_ => None,
},
}
}
}
struct StringExpression<'a>(&'a Expression);
impl LookupObject for StringExpression<'_> {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let member_function = |f: BuiltinFunction| {
LookupResult::Callable(LookupResultCallable::MemberFunction {
base: self.0.clone(),
base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
member: LookupResultCallable::Callable(Callable::Builtin(f)).into(),
})
};
let function_call = |f: BuiltinFunction| {
LookupResult::from(Expression::FunctionCall {
function: Callable::Builtin(f),
source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
arguments: vec![self.0.clone()],
})
};
let mut f = |s, res| f(&SmolStr::new_static(s), res);
None.or_else(|| f("is-float", member_function(BuiltinFunction::StringIsFloat)))
.or_else(|| f("to-float", member_function(BuiltinFunction::StringToFloat)))
.or_else(|| f("is-empty", function_call(BuiltinFunction::StringIsEmpty)))
.or_else(|| f("character-count", function_call(BuiltinFunction::StringCharacterCount)))
.or_else(|| f("to-lowercase", member_function(BuiltinFunction::StringToLowercase)))
.or_else(|| f("to-uppercase", member_function(BuiltinFunction::StringToUppercase)))
}
}
struct ColorExpression<'a>(&'a Expression);
impl LookupObject for ColorExpression<'_> {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let member_function = |f: BuiltinFunction| {
let base = if f == BuiltinFunction::ColorHsvaStruct && self.0.ty() == Type::Brush {
Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
} else {
self.0.clone()
};
LookupResult::Callable(LookupResultCallable::MemberFunction {
base,
base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
member: Box::new(LookupResultCallable::Callable(Callable::Builtin(f))),
})
};
let field_access = |f: &'static str| {
let base = if self.0.ty() == Type::Brush {
Expression::Cast { from: Box::new(self.0.clone()), to: Type::Color }
} else {
self.0.clone()
};
LookupResult::from(Expression::StructFieldAccess {
base: Box::new(Expression::FunctionCall {
function: BuiltinFunction::ColorRgbaStruct.into(),
source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
arguments: vec![base],
}),
name: SmolStr::new_static(f),
})
};
let mut f = |s, res| f(&SmolStr::new_static(s), res);
None.or_else(|| f("red", field_access("red")))
.or_else(|| f("green", field_access("green")))
.or_else(|| f("blue", field_access("blue")))
.or_else(|| f("alpha", field_access("alpha")))
.or_else(|| f("to-hsv", member_function(BuiltinFunction::ColorHsvaStruct)))
.or_else(|| f("brighter", member_function(BuiltinFunction::ColorBrighter)))
.or_else(|| f("darker", member_function(BuiltinFunction::ColorDarker)))
.or_else(|| f("transparentize", member_function(BuiltinFunction::ColorTransparentize)))
.or_else(|| f("with-alpha", member_function(BuiltinFunction::ColorWithAlpha)))
.or_else(|| f("mix", member_function(BuiltinFunction::ColorMix)))
}
}
struct ImageExpression<'a>(&'a Expression);
impl LookupObject for ImageExpression<'_> {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let field_access = |f: &str| {
LookupResult::from(Expression::StructFieldAccess {
base: Box::new(Expression::FunctionCall {
function: BuiltinFunction::ImageSize.into(),
source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
arguments: vec![self.0.clone()],
}),
name: f.into(),
})
};
let mut f = |s, res| f(&SmolStr::new_static(s), res);
None.or_else(|| f("width", field_access("width")))
.or_else(|| f("height", field_access("height")))
}
}
struct ArrayExpression<'a>(&'a Expression);
impl LookupObject for ArrayExpression<'_> {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let member_function = |f: BuiltinFunction| {
LookupResult::from(Expression::FunctionCall {
function: Callable::Builtin(f),
source_location: ctx.current_token.as_ref().map(|t| t.to_source_location()),
arguments: vec![self.0.clone()],
})
};
None.or_else(|| {
f(&SmolStr::new_static("length"), member_function(BuiltinFunction::ArrayLength))
})
}
}
/// An expression of type int or float
struct NumberExpression<'a>(&'a Expression);
impl LookupObject for NumberExpression<'_> {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let member_function = |f: BuiltinFunction| {
LookupResult::Callable(LookupResultCallable::MemberFunction {
base: self.0.clone(),
base_node: ctx.current_token.clone(), // Note that this is not the base_node, but the function's node
member: LookupResultCallable::Callable(Callable::Builtin(f)).into(),
})
};
let mut f2 = |s, res| f(&SmolStr::new_static(s), res);
None.or_else(|| f2("round", member_function(BuiltinFunction::Round)))
.or_else(|| f2("ceil", member_function(BuiltinFunction::Ceil)))
.or_else(|| f2("floor", member_function(BuiltinFunction::Floor)))
.or_else(|| f2("sqrt", member_function(BuiltinFunction::Sqrt)))
.or_else(|| f2("asin", member_function(BuiltinFunction::ASin)))
.or_else(|| f2("acos", member_function(BuiltinFunction::ACos)))
.or_else(|| f2("atan", member_function(BuiltinFunction::ATan)))
.or_else(|| f2("log", member_function(BuiltinFunction::Log)))
.or_else(|| f2("pow", member_function(BuiltinFunction::Pow)))
.or_else(|| f2("to-fixed", member_function(BuiltinFunction::ToFixed)))
.or_else(|| f2("to-precision", member_function(BuiltinFunction::ToPrecision)))
.or_else(|| NumberWithUnitExpression(self.0).for_each_entry(ctx, f))
}
}
/// An expression of any numerical value with an unit
struct NumberWithUnitExpression<'a>(&'a Expression);
impl LookupObject for NumberWithUnitExpression<'_> {
fn for_each_entry<R>(
&self,
ctx: &LookupCtx,
f: &mut impl FnMut(&SmolStr, LookupResult) -> Option<R>,
) -> Option<R> {
let member_macro = |f: BuiltinMacroFunction| {
LookupResult::Callable(LookupResultCallable::MemberFunction {
base: 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(LookupResultCallable::Macro(f)),
})
};
let mut f = |s, res| f(&SmolStr::new_static(s), res);
None.or_else(|| f("mod", member_macro(BuiltinMacroFunction::Mod)))
.or_else(|| f("clamp", member_macro(BuiltinMacroFunction::Clamp)))
.or_else(|| f("abs", member_macro(BuiltinMacroFunction::Abs)))
.or_else(|| f("max", member_macro(BuiltinMacroFunction::Max)))
.or_else(|| f("min", member_macro(BuiltinMacroFunction::Min)))
.or_else(|| {
if self.0.ty() != Type::Angle {
return None;
}
let member_function = |f: BuiltinFunction| {
LookupResult::Callable(LookupResultCallable::MemberFunction {
base: 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(LookupResultCallable::Callable(Callable::Builtin(f))),
})
};
None.or_else(|| f("sin", member_function(BuiltinFunction::Sin)))
.or_else(|| f("cos", member_function(BuiltinFunction::Cos)))
.or_else(|| f("tan", member_function(BuiltinFunction::Tan)))
})
}
}