// Copyright © SixtyFPS GmbH // SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0 //! This module contains the implementation of the builtin macros. //! They are just transformations that convert into some more complicated expression tree use crate::diagnostics::{BuildDiagnostics, Spanned}; use crate::expression_tree::{ BuiltinFunction, BuiltinMacroFunction, Callable, EasingCurve, Expression, MinMaxOp, Unit, }; use crate::langtype::{EnumerationValue, Type}; use crate::parser::NodeOrToken; use smol_str::{format_smolstr, ToSmolStr}; /// Used for uniquely name some variables static COUNTER: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(1); /// "Expand" the macro `mac` (at location `n`) with the arguments `sub_expr` pub fn lower_macro( mac: BuiltinMacroFunction, n: &dyn Spanned, mut sub_expr: impl Iterator)>, diag: &mut BuildDiagnostics, ) -> Expression { match mac { BuiltinMacroFunction::Min => min_max_macro(n, MinMaxOp::Min, sub_expr.collect(), diag), BuiltinMacroFunction::Max => min_max_macro(n, MinMaxOp::Max, sub_expr.collect(), diag), BuiltinMacroFunction::Clamp => clamp_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::Mod => mod_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::Abs => abs_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::Debug => debug_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::CubicBezier => { let mut has_error = None; let expected_argument_type_error = "Arguments to cubic bezier curve must be number literal"; // FIXME: this is not pretty to be handling there. // Maybe "cubic_bezier" should be a function that is lowered later let mut a = || match sub_expr.next() { None => { has_error.get_or_insert((n.to_source_location(), "Not enough arguments")); 0. } Some((Expression::NumberLiteral(val, Unit::None), _)) => val as f32, // handle negative numbers Some((Expression::UnaryOp { sub, op: '-' }, n)) => match *sub { Expression::NumberLiteral(val, Unit::None) => (-1.0 * val) as f32, _ => { has_error .get_or_insert((n.to_source_location(), expected_argument_type_error)); 0. } }, Some((_, n)) => { has_error.get_or_insert((n.to_source_location(), expected_argument_type_error)); 0. } }; let expr = Expression::EasingCurve(EasingCurve::CubicBezier(a(), a(), a(), a())); if let Some((_, n)) = sub_expr.next() { has_error .get_or_insert((n.to_source_location(), "Too many argument for bezier curve")); } if let Some((n, msg)) = has_error { diag.push_error(msg.into(), &n); } expr } BuiltinMacroFunction::Rgb => rgb_macro(n, sub_expr.collect(), diag), BuiltinMacroFunction::Hsv => hsv_macro(n, sub_expr.collect(), diag), } } fn min_max_macro( node: &dyn Spanned, op: MinMaxOp, args: Vec<(Expression, Option)>, diag: &mut BuildDiagnostics, ) -> Expression { if args.is_empty() { diag.push_error("Needs at least one argument".into(), node); return Expression::Invalid; } let ty = Expression::common_target_type_for_type_list(args.iter().map(|expr| expr.0.ty())); if ty.as_unit_product().is_none() { diag.push_error("Invalid argument type".into(), node); return Expression::Invalid; } let mut args = args.into_iter(); let (base, arg_node) = args.next().unwrap(); let mut base = base.maybe_convert_to(ty.clone(), &arg_node, diag); for (next, arg_node) in args { let rhs = next.maybe_convert_to(ty.clone(), &arg_node, diag); base = min_max_expression(base, rhs, op); } base } fn clamp_macro( node: &dyn Spanned, args: Vec<(Expression, Option)>, diag: &mut BuildDiagnostics, ) -> Expression { if args.len() != 3 { diag.push_error( "`clamp` needs three values: the `value` to clamp, the `minimum` and the `maximum`" .into(), node, ); return Expression::Invalid; } let (value, value_node) = args.first().unwrap().clone(); let ty = value.ty(); if ty.as_unit_product().is_none() { diag.push_error("Invalid argument type".into(), &value_node); return Expression::Invalid; } let (min, min_node) = args.get(1).unwrap().clone(); let min = min.maybe_convert_to(ty.clone(), &min_node, diag); let (max, max_node) = args.get(2).unwrap().clone(); let max = max.maybe_convert_to(ty.clone(), &max_node, diag); let value = min_max_expression(value, max, MinMaxOp::Min); min_max_expression(min, value, MinMaxOp::Max) } fn mod_macro( node: &dyn Spanned, args: Vec<(Expression, Option)>, diag: &mut BuildDiagnostics, ) -> Expression { if args.len() != 2 { diag.push_error("Needs 2 arguments".into(), node); return Expression::Invalid; } let (lhs_ty, rhs_ty) = (args[0].0.ty(), args[1].0.ty()); let common_ty = if lhs_ty.default_unit().is_some() { lhs_ty } else if rhs_ty.default_unit().is_some() { rhs_ty } else if matches!(lhs_ty, Type::UnitProduct(_)) { lhs_ty } else if matches!(rhs_ty, Type::UnitProduct(_)) { rhs_ty } else { Type::Float32 }; let source_location = Some(node.to_source_location()); let function = Callable::Builtin(BuiltinFunction::Mod); let arguments = args.into_iter().map(|(e, n)| e.maybe_convert_to(common_ty.clone(), &n, diag)); if matches!(common_ty, Type::Float32) { Expression::FunctionCall { function, arguments: arguments.collect(), source_location } } else { Expression::Cast { from: Expression::FunctionCall { function, arguments: arguments .map(|a| Expression::Cast { from: a.into(), to: Type::Float32 }) .collect(), source_location, } .into(), to: common_ty.clone(), } } } fn abs_macro( node: &dyn Spanned, args: Vec<(Expression, Option)>, diag: &mut BuildDiagnostics, ) -> Expression { if args.len() != 1 { diag.push_error("Needs 1 argument".into(), node); return Expression::Invalid; } let ty = args[0].0.ty(); let ty = if ty.default_unit().is_some() || matches!(ty, Type::UnitProduct(_)) { ty } else { Type::Float32 }; let source_location = Some(node.to_source_location()); let function = Callable::Builtin(BuiltinFunction::Abs); if matches!(ty, Type::Float32) { let arguments = args.into_iter().map(|(e, n)| e.maybe_convert_to(ty.clone(), &n, diag)).collect(); Expression::FunctionCall { function, arguments, source_location } } else { Expression::Cast { from: Expression::FunctionCall { function, arguments: args .into_iter() .map(|(a, _)| Expression::Cast { from: a.into(), to: Type::Float32 }) .collect(), source_location, } .into(), to: ty, } } } fn rgb_macro( node: &dyn Spanned, args: Vec<(Expression, Option)>, diag: &mut BuildDiagnostics, ) -> Expression { if args.len() < 3 || args.len() > 4 { diag.push_error( format!("This function needs 3 or 4 arguments, but {} were provided", args.len()), node, ); return Expression::Invalid; } let mut arguments: Vec<_> = args .into_iter() .enumerate() .map(|(i, (expr, n))| { if i < 3 { if expr.ty() == Type::Percent { Expression::BinaryExpression { lhs: Box::new(expr.maybe_convert_to(Type::Float32, &n, diag)), rhs: Box::new(Expression::NumberLiteral(255., Unit::None)), op: '*', } } else { expr.maybe_convert_to(Type::Float32, &n, diag) } } else { expr.maybe_convert_to(Type::Float32, &n, diag) } }) .collect(); if arguments.len() < 4 { arguments.push(Expression::NumberLiteral(1., Unit::None)) } Expression::FunctionCall { function: BuiltinFunction::Rgb.into(), arguments, source_location: Some(node.to_source_location()), } } fn hsv_macro( node: &dyn Spanned, args: Vec<(Expression, Option)>, diag: &mut BuildDiagnostics, ) -> Expression { if args.len() < 3 || args.len() > 4 { diag.push_error( format!("This function needs 3 or 4 arguments, but {} were provided", args.len()), node, ); return Expression::Invalid; } let mut arguments: Vec<_> = args.into_iter().map(|(expr, n)| expr.maybe_convert_to(Type::Float32, &n, diag)).collect(); if arguments.len() < 4 { arguments.push(Expression::NumberLiteral(1., Unit::None)) } Expression::FunctionCall { function: BuiltinFunction::Hsv.into(), arguments, source_location: Some(node.to_source_location()), } } fn debug_macro( node: &dyn Spanned, args: Vec<(Expression, Option)>, diag: &mut BuildDiagnostics, ) -> Expression { let mut string = None; for (expr, node) in args { let val = to_debug_string(expr, &node, diag); string = Some(match string { None => val, Some(string) => Expression::BinaryExpression { lhs: Box::new(string), op: '+', rhs: Box::new(Expression::BinaryExpression { lhs: Box::new(Expression::StringLiteral(" ".into())), op: '+', rhs: Box::new(val), }), }, }); } Expression::FunctionCall { function: BuiltinFunction::Debug.into(), arguments: vec![string.unwrap_or_else(|| Expression::default_value_for_type(&Type::String))], source_location: Some(node.to_source_location()), } } fn to_debug_string( expr: Expression, node: &dyn Spanned, diag: &mut BuildDiagnostics, ) -> Expression { let ty = expr.ty(); match &ty { Type::Invalid => Expression::Invalid, Type::Void | Type::InferredCallback | Type::InferredProperty | Type::Callback { .. } | Type::ComponentFactory | Type::Function { .. } | Type::ElementReference | Type::LayoutCache | Type::Model | Type::PathData => { diag.push_error("Cannot debug this expression".into(), node); Expression::Invalid } Type::Float32 | Type::Int32 => expr.maybe_convert_to(Type::String, node, diag), Type::String => expr, // TODO Type::Color | Type::Brush | Type::Image | Type::Easing | Type::Array(_) => { Expression::StringLiteral("".into()) } Type::Duration | Type::PhysicalLength | Type::LogicalLength | Type::Rem | Type::Angle | Type::Percent | Type::UnitProduct(_) => Expression::BinaryExpression { lhs: Box::new( Expression::Cast { from: Box::new(expr), to: Type::Float32 }.maybe_convert_to( Type::String, node, diag, ), ), op: '+', rhs: Box::new(Expression::StringLiteral( Type::UnitProduct(ty.as_unit_product().unwrap()).to_smolstr(), )), }, Type::Bool => Expression::Condition { condition: Box::new(expr), true_expr: Box::new(Expression::StringLiteral("true".into())), false_expr: Box::new(Expression::StringLiteral("false".into())), }, Type::Struct(s) => { let local_object = format_smolstr!( "debug_struct{}", COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) ); let mut string = None; for k in s.fields.keys() { let field_name = if string.is_some() { format_smolstr!(", {}: ", k) } else { format_smolstr!("{{ {}: ", k) }; let value = to_debug_string( Expression::StructFieldAccess { base: Box::new(Expression::ReadLocalVariable { name: local_object.clone(), ty: ty.clone(), }), name: k.clone(), }, node, diag, ); let field = Expression::BinaryExpression { lhs: Box::new(Expression::StringLiteral(field_name)), op: '+', rhs: Box::new(value), }; string = Some(match string { None => field, Some(x) => Expression::BinaryExpression { lhs: Box::new(x), op: '+', rhs: Box::new(field), }, }); } match string { None => Expression::StringLiteral("{}".into()), Some(string) => Expression::CodeBlock(vec![ Expression::StoreLocalVariable { name: local_object, value: Box::new(expr) }, Expression::BinaryExpression { lhs: Box::new(string), op: '+', rhs: Box::new(Expression::StringLiteral(" }".into())), }, ]), } } Type::Enumeration(enu) => { let local_object = "debug_enum"; let mut v = vec![Expression::StoreLocalVariable { name: local_object.into(), value: Box::new(expr), }]; let mut cond = Expression::StringLiteral(format_smolstr!("Error: invalid value for {}", ty)); for (idx, val) in enu.values.iter().enumerate() { cond = Expression::Condition { condition: Box::new(Expression::BinaryExpression { lhs: Box::new(Expression::ReadLocalVariable { name: local_object.into(), ty: ty.clone(), }), rhs: Box::new(Expression::EnumerationValue(EnumerationValue { value: idx, enumeration: enu.clone(), })), op: '=', }), true_expr: Box::new(Expression::StringLiteral(val.clone())), false_expr: Box::new(cond), }; } v.push(cond); Expression::CodeBlock(v) } } } /// Generate an expression which is like `min(lhs, rhs)` if op is '<' or `max(lhs, rhs)` if op is '>'. /// counter is an unique id. /// The rhs and lhs of the expression must have the same numerical type pub fn min_max_expression(lhs: Expression, rhs: Expression, op: MinMaxOp) -> Expression { let lhs_ty = lhs.ty(); let rhs_ty = rhs.ty(); let ty = match (lhs_ty, rhs_ty) { (a, b) if a == b => a, (Type::Int32, Type::Float32) | (Type::Float32, Type::Int32) => Type::Float32, _ => Type::Invalid, }; Expression::MinMax { ty, op, lhs: Box::new(lhs), rhs: Box::new(rhs) } }