mirror of
https://github.com/slint-ui/slint.git
synced 2025-08-02 18:03:07 +00:00

These are two different concept, and it is confusing to keep them in the same enum We want to support component without any base element, and Void is already used for global component, so do this refactoring before
360 lines
13 KiB
Rust
360 lines
13 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint-ui.com>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-commercial
|
|
|
|
//! 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, EasingCurve, Expression, Unit,
|
|
};
|
|
use crate::langtype::{EnumerationValue, Type};
|
|
use crate::parser::NodeOrToken;
|
|
|
|
/// 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: Option<NodeOrToken>,
|
|
mut sub_expr: impl Iterator<Item = (Expression, Option<NodeOrToken>)>,
|
|
diag: &mut BuildDiagnostics,
|
|
) -> Expression {
|
|
match mac {
|
|
BuiltinMacroFunction::Min => min_max_macro(n, '<', sub_expr.collect(), diag),
|
|
BuiltinMacroFunction::Max => min_max_macro(n, '>', sub_expr.collect(), diag),
|
|
BuiltinMacroFunction::Mod => mod_macro(n, sub_expr.collect(), diag),
|
|
BuiltinMacroFunction::Debug => debug_macro(n, sub_expr.collect(), diag),
|
|
BuiltinMacroFunction::CubicBezier => {
|
|
let mut has_error = None;
|
|
// 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.clone(), "Not enough arguments"));
|
|
0.
|
|
}
|
|
Some((Expression::NumberLiteral(val, Unit::None), _)) => val as f32,
|
|
Some((_, n)) => {
|
|
has_error.get_or_insert((
|
|
n,
|
|
"Arguments to cubic bezier curve must be number literal",
|
|
));
|
|
0.
|
|
}
|
|
};
|
|
let expr = Expression::EasingCurve(EasingCurve::CubicBezier(a(), a(), a(), a()));
|
|
if let Some((_, n)) = sub_expr.next() {
|
|
has_error.get_or_insert((n, "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),
|
|
}
|
|
}
|
|
|
|
fn min_max_macro(
|
|
node: Option<NodeOrToken>,
|
|
op: char,
|
|
args: Vec<(Expression, Option<NodeOrToken>)>,
|
|
diag: &mut BuildDiagnostics,
|
|
) -> Expression {
|
|
if args.is_empty() {
|
|
diag.push_error("Needs at least one argument".into(), &node);
|
|
return Expression::Invalid;
|
|
}
|
|
let mut args = args.into_iter();
|
|
let (mut base, arg_node) = args.next().unwrap();
|
|
let ty = match base.ty() {
|
|
Type::Float32 => Type::Float32,
|
|
// In case there are other floats, we don't want to convert the result to int
|
|
Type::Int32 => Type::Float32,
|
|
Type::PhysicalLength => Type::PhysicalLength,
|
|
Type::LogicalLength => Type::LogicalLength,
|
|
Type::Duration => Type::Duration,
|
|
Type::Angle => Type::Angle,
|
|
Type::Percent => Type::Float32,
|
|
_ => {
|
|
diag.push_error("Invalid argument type".into(), &arg_node);
|
|
return Expression::Invalid;
|
|
}
|
|
};
|
|
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 mod_macro(
|
|
node: Option<NodeOrToken>,
|
|
args: Vec<(Expression, Option<NodeOrToken>)>,
|
|
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 = node.map(|n| n.to_source_location());
|
|
let function = Box::new(Expression::BuiltinFunctionReference(
|
|
BuiltinFunction::Mod,
|
|
source_location.clone(),
|
|
));
|
|
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 rgb_macro(
|
|
node: Option<NodeOrToken>,
|
|
args: Vec<(Expression, Option<NodeOrToken>)>,
|
|
diag: &mut BuildDiagnostics,
|
|
) -> Expression {
|
|
if args.len() < 3 {
|
|
diag.push_error("Needs 3 or 4 argument".into(), &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::Int32, &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: Box::new(Expression::BuiltinFunctionReference(
|
|
BuiltinFunction::Rgb,
|
|
node.as_ref().map(|t| t.to_source_location()),
|
|
)),
|
|
arguments,
|
|
source_location: Some(node.to_source_location()),
|
|
}
|
|
}
|
|
|
|
fn debug_macro(
|
|
node: Option<NodeOrToken>,
|
|
args: Vec<(Expression, Option<NodeOrToken>)>,
|
|
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),
|
|
}),
|
|
},
|
|
});
|
|
}
|
|
let sl = node.map(|node| node.to_source_location());
|
|
Expression::FunctionCall {
|
|
function: Box::new(Expression::BuiltinFunctionReference(
|
|
BuiltinFunction::Debug,
|
|
sl.clone(),
|
|
)),
|
|
arguments: vec![string.unwrap_or_else(|| Expression::default_value_for_type(&Type::String))],
|
|
source_location: sl,
|
|
}
|
|
}
|
|
|
|
fn to_debug_string(
|
|
expr: Expression,
|
|
node: Option<NodeOrToken>,
|
|
diag: &mut BuildDiagnostics,
|
|
) -> Expression {
|
|
let ty = expr.ty();
|
|
match &ty {
|
|
Type::Invalid => Expression::Invalid,
|
|
Type::Void
|
|
| Type::InferredCallback
|
|
| Type::InferredProperty
|
|
| Type::Callback { .. }
|
|
| 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("<debug-of-this-type-not-yet-implemented>".into())
|
|
}
|
|
Type::Duration
|
|
| Type::PhysicalLength
|
|
| Type::LogicalLength
|
|
| 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_string(),
|
|
)),
|
|
},
|
|
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 { fields, .. } => {
|
|
let local_object = format!(
|
|
"debug_struct{}",
|
|
COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
|
|
);
|
|
let mut string = None;
|
|
for k in fields.keys() {
|
|
let field_name =
|
|
if string.is_some() { format!(", {}: ", k) } else { format!("{{ {}: ", k) };
|
|
let value = to_debug_string(
|
|
Expression::StructFieldAccess {
|
|
base: Box::new(Expression::ReadLocalVariable {
|
|
name: local_object.clone(),
|
|
ty: ty.clone(),
|
|
}),
|
|
name: k.clone(),
|
|
},
|
|
node.clone(),
|
|
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!("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: char) -> Expression {
|
|
let ty = lhs.ty();
|
|
let id = COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
|
let n1 = format!("minmax_lhs{}", id);
|
|
let n2 = format!("minmax_rhs{}", id);
|
|
let a1 = Box::new(Expression::ReadLocalVariable { name: n1.clone(), ty: ty.clone() });
|
|
let a2 = Box::new(Expression::ReadLocalVariable { name: n2.clone(), ty });
|
|
Expression::CodeBlock(vec![
|
|
Expression::StoreLocalVariable { name: n1, value: Box::new(lhs) },
|
|
Expression::StoreLocalVariable { name: n2, value: Box::new(rhs) },
|
|
Expression::Condition {
|
|
condition: Box::new(Expression::BinaryExpression {
|
|
lhs: a1.clone(),
|
|
rhs: a2.clone(),
|
|
op,
|
|
}),
|
|
true_expr: a1,
|
|
false_expr: a2,
|
|
},
|
|
])
|
|
}
|