Skip to main content

ferroui_base/rendering/composition/expressions/
expression.rs

1//! The syntax tree of a parsed composition expression.
2//!
3//! The reference implementation is a class hierarchy; here the tree is the
4//! [`Expression`] enum, one variant (and one struct) per node class. A tree
5//! is plain data (`Send + Sync`): it is built on the UI thread and evaluated
6//! by the server compositor.
7
8use std::collections::HashSet;
9use std::fmt;
10
11use super::expression_evaluation_context::ExpressionEvaluationContext;
12use super::expression_parse_exception::ExpressionParseException;
13use super::expression_parser::ExpressionParser;
14use super::expression_variant::ExpressionVariant;
15use crate::numerics::InvariantF32;
16
17/// A parsed composition expression.
18#[derive(Clone, Debug, PartialEq)]
19pub enum Expression {
20    Conditional(ConditionalExpression),
21    Constant(ConstantExpression),
22    FunctionCall(FunctionCallExpression),
23    MemberAccess(MemberAccessExpression),
24    Parameter(ParameterExpression),
25    Keyword(KeywordExpression),
26    Unary(UnaryExpression),
27    Binary(BinaryExpression),
28}
29
30impl Expression {
31    /// The kind of the node.
32    pub fn expression_type(&self) -> ExpressionType {
33        match self {
34            Expression::Conditional(_) => ExpressionType::ConditionalExpression,
35            Expression::Constant(_) => ExpressionType::Constant,
36            Expression::FunctionCall(_) => ExpressionType::FunctionCall,
37            Expression::MemberAccess(_) => ExpressionType::MemberAccess,
38            Expression::Parameter(_) => ExpressionType::Parameter,
39            Expression::Keyword(_) => ExpressionType::Keyword,
40            Expression::Unary(e) => e.expression_type,
41            Expression::Binary(e) => e.expression_type,
42        }
43    }
44
45    /// Parses an expression.
46    pub fn parse(expression: &str) -> Result<Expression, ExpressionParseException> {
47        ExpressionParser::parse(expression)
48    }
49
50    /// Evaluates the expression. Anything that cannot be evaluated (an
51    /// unknown parameter, member or function, mismatched operand types)
52    /// gives an invalid variant.
53    ///
54    /// # Panics
55    /// Panics when the expression reads a member of `this.Target` and the
56    /// context has no target, or calls a function and the context has no
57    /// foreign function interface (programmer errors).
58    pub fn evaluate(&self, context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
59        match self {
60            Expression::Conditional(e) => e.evaluate(context),
61            Expression::Constant(e) => e.evaluate(context),
62            Expression::FunctionCall(e) => e.evaluate(context),
63            Expression::MemberAccess(e) => e.evaluate(context),
64            Expression::Parameter(e) => e.evaluate(context),
65            Expression::Keyword(e) => e.evaluate(context),
66            Expression::Unary(e) => e.evaluate(context),
67            Expression::Binary(e) => e.evaluate(context),
68        }
69    }
70
71    /// Collects the `(parameter, property)` pairs the expression reads.
72    /// Members of `this.Target` are collected with the parameter name
73    /// [`ExpressionKeywords::TARGET`].
74    pub fn collect_references(&self, references: &mut HashSet<(String, String)>) {
75        match self {
76            Expression::Conditional(e) => e.collect_references(references),
77            Expression::FunctionCall(e) => e.collect_references(references),
78            Expression::MemberAccess(e) => e.collect_references(references),
79            Expression::Unary(e) => e.collect_references(references),
80            Expression::Binary(e) => e.collect_references(references),
81            Expression::Constant(_) | Expression::Parameter(_) | Expression::Keyword(_) => {}
82        }
83    }
84
85    /// The text of an operator, or the name of the expression type for the
86    /// types that are not operators.
87    pub fn operator_name(t: ExpressionType) -> &'static str {
88        match t {
89            ExpressionType::Add => "+",
90            ExpressionType::Subtract => "-",
91            ExpressionType::Divide => "/",
92            ExpressionType::Multiply => "*",
93            ExpressionType::MoreThan => ">",
94            ExpressionType::LessThan => "<",
95            ExpressionType::MoreThanOrEqual => ">=",
96            ExpressionType::LessThanOrEqual => "<=",
97            ExpressionType::LogicalAnd => "&&",
98            ExpressionType::LogicalOr => "||",
99            ExpressionType::Remainder => "%",
100            // The reference implementation looks the operator text up by the
101            // name of the enumeration member, and for this member the lookup
102            // finds the inherited `Equals` method first, so the name of the
103            // member is used instead of `==`.
104            ExpressionType::Equals => "Equals",
105            ExpressionType::NotEquals => "!=",
106            ExpressionType::Not => "!",
107            ExpressionType::UnaryMinus => "-",
108            ExpressionType::MemberAccess => "MemberAccess",
109            ExpressionType::Parameter => "Parameter",
110            ExpressionType::FunctionCall => "FunctionCall",
111            ExpressionType::Keyword => "Keyword",
112            ExpressionType::Constant => "Constant",
113            ExpressionType::ConditionalExpression => "ConditionalExpression",
114        }
115    }
116}
117
118impl fmt::Display for Expression {
119    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
120        match self {
121            Expression::Conditional(e) => e.fmt(f),
122            Expression::Constant(e) => e.fmt(f),
123            Expression::FunctionCall(e) => e.fmt(f),
124            Expression::MemberAccess(e) => e.fmt(f),
125            Expression::Parameter(e) => e.fmt(f),
126            Expression::Keyword(e) => e.fmt(f),
127            Expression::Unary(e) => e.fmt(f),
128            Expression::Binary(e) => e.fmt(f),
129        }
130    }
131}
132
133macro_rules! into_expression {
134    ($($variant:ident($ty:ident)),+ $(,)?) => {
135        $(impl From<$ty> for Expression {
136            #[inline]
137            fn from(value: $ty) -> Expression {
138                Expression::$variant(value)
139            }
140        })+
141    };
142}
143
144into_expression!(
145    Conditional(ConditionalExpression),
146    Constant(ConstantExpression),
147    FunctionCall(FunctionCallExpression),
148    MemberAccess(MemberAccessExpression),
149    Parameter(ParameterExpression),
150    Keyword(KeywordExpression),
151    Unary(UnaryExpression),
152    Binary(BinaryExpression),
153);
154
155/// The kind of an expression node (for unary and binary nodes, the operator).
156#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
157pub enum ExpressionType {
158    // Binary operators
159    Add,
160    Subtract,
161    Divide,
162    Multiply,
163    MoreThan,
164    LessThan,
165    MoreThanOrEqual,
166    LessThanOrEqual,
167    LogicalAnd,
168    LogicalOr,
169    Remainder,
170    Equals,
171    NotEquals,
172    // Unary operators
173    Not,
174    UnaryMinus,
175    // The rest
176    MemberAccess,
177    Parameter,
178    FunctionCall,
179    Keyword,
180    Constant,
181    ConditionalExpression,
182}
183
184/// The keywords of the expression language.
185#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
186pub enum ExpressionKeyword {
187    StartingValue,
188    CurrentValue,
189    FinalValue,
190    Target,
191    Pi,
192    True,
193    False,
194}
195
196/// The (lower-case) text of the keywords of the expression language.
197pub struct ExpressionKeywords;
198
199impl ExpressionKeywords {
200    pub const STARTING_VALUE: &'static str = "this.startingvalue";
201    pub const CURRENT_VALUE: &'static str = "this.currentvalue";
202    pub const FINAL_VALUE: &'static str = "this.finalvalue";
203    pub const PI: &'static str = "pi";
204    pub const TRUE: &'static str = "true";
205    pub const FALSE: &'static str = "false";
206    pub const TARGET: &'static str = "this.target";
207}
208
209/// `condition ? true_part : false_part`.
210#[derive(Clone, Debug, PartialEq)]
211pub struct ConditionalExpression {
212    pub condition: Box<Expression>,
213    pub true_part: Box<Expression>,
214    pub false_part: Box<Expression>,
215}
216
217impl ConditionalExpression {
218    pub fn new(condition: Expression, true_part: Expression, false_part: Expression) -> Self {
219        Self {
220            condition: Box::new(condition),
221            true_part: Box::new(true_part),
222            false_part: Box::new(false_part),
223        }
224    }
225
226    pub fn evaluate(&self, context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
227        let cond = self.condition.evaluate(context);
228        if cond == ExpressionVariant::Boolean(true) {
229            return self.true_part.evaluate(context);
230        }
231        self.false_part.evaluate(context)
232    }
233
234    pub fn collect_references(&self, references: &mut HashSet<(String, String)>) {
235        self.condition.collect_references(references);
236        self.true_part.collect_references(references);
237        self.false_part.collect_references(references);
238    }
239}
240
241impl fmt::Display for ConditionalExpression {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        write!(f, "({}) ? ({}) : ({})", self.condition, self.true_part, self.false_part)
244    }
245}
246
247/// A numeric literal.
248#[derive(Clone, Debug, PartialEq)]
249pub struct ConstantExpression {
250    pub constant: f32,
251}
252
253impl ConstantExpression {
254    pub fn new(constant: f32) -> Self {
255        Self { constant }
256    }
257
258    pub fn evaluate(&self, _context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
259        self.constant.into()
260    }
261}
262
263impl fmt::Display for ConstantExpression {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        InvariantF32(self.constant).fmt(f)
266    }
267}
268
269/// A call of a foreign function.
270#[derive(Clone, Debug, PartialEq)]
271pub struct FunctionCallExpression {
272    pub name: String,
273    pub parameters: Vec<Expression>,
274}
275
276impl FunctionCallExpression {
277    pub fn new(name: impl Into<String>, parameters: Vec<Expression>) -> Self {
278        Self {
279            name: name.into(),
280            parameters,
281        }
282    }
283
284    pub fn evaluate(&self, context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
285        let mut args = Vec::with_capacity(self.parameters.len());
286        for expr in &self.parameters {
287            args.push(expr.evaluate(context));
288        }
289        context
290            .foreign_function_interface
291            .expect("the expression evaluation context has no foreign function interface")
292            .call(&self.name, &args)
293            .unwrap_or_default()
294    }
295
296    pub fn collect_references(&self, references: &mut HashSet<(String, String)>) {
297        for arg in &self.parameters {
298            arg.collect_references(references);
299        }
300    }
301}
302
303impl fmt::Display for FunctionCallExpression {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        write!(f, "{}( (", self.name)?;
306        for (i, parameter) in self.parameters.iter().enumerate() {
307            if i != 0 {
308                f.write_str("), (")?;
309            }
310            parameter.fmt(f)?;
311        }
312        f.write_str(") )")
313    }
314}
315
316/// `target.member`.
317#[derive(Clone, Debug, PartialEq)]
318pub struct MemberAccessExpression {
319    pub target: Box<Expression>,
320    pub member: String,
321}
322
323impl MemberAccessExpression {
324    pub fn new(target: Expression, member: impl Into<String>) -> Self {
325        Self {
326            target: Box::new(target),
327            member: member.into(),
328        }
329    }
330
331    pub fn collect_references(&self, references: &mut HashSet<(String, String)>) {
332        self.target.collect_references(references);
333        match &*self.target {
334            Expression::Parameter(pe) => {
335                references.insert((pe.name.clone(), self.member.clone()));
336            }
337            Expression::Keyword(KeywordExpression {
338                keyword: ExpressionKeyword::Target,
339            }) => {
340                references.insert((ExpressionKeywords::TARGET.to_owned(), self.member.clone()));
341            }
342            _ => {}
343        }
344    }
345
346    pub fn evaluate(&self, context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
347        match &*self.target {
348            Expression::Keyword(ke) if ke.keyword == ExpressionKeyword::Target => {
349                return context
350                    .target
351                    .expect("the expression evaluation context has no target")
352                    .get_property(&self.member);
353            }
354            Expression::Parameter(pe) => {
355                if let Some(obj) = context.parameters.and_then(|p| p.get_object_parameter(&pe.name)) {
356                    return obj.get_property(&self.member);
357                }
358            }
359            _ => {}
360        }
361        // Those are considered immutable
362        self.target.evaluate(context).get_property(&self.member)
363    }
364}
365
366impl fmt::Display for MemberAccessExpression {
367    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
368        write!(f, "({}).{}", self.target, self.member)
369    }
370}
371
372/// A reference to a parameter of the animation.
373#[derive(Clone, Debug, PartialEq)]
374pub struct ParameterExpression {
375    pub name: String,
376}
377
378impl ParameterExpression {
379    pub fn new(name: impl Into<String>) -> Self {
380        Self { name: name.into() }
381    }
382
383    pub fn evaluate(&self, context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
384        context
385            .parameters
386            .map(|p| p.get_parameter(&self.name))
387            .unwrap_or_default()
388    }
389}
390
391impl fmt::Display for ParameterExpression {
392    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
393        write!(f, "{{{}}}", self.name)
394    }
395}
396
397/// A keyword of the expression language.
398#[derive(Clone, Debug, PartialEq)]
399pub struct KeywordExpression {
400    pub keyword: ExpressionKeyword,
401}
402
403impl KeywordExpression {
404    pub fn new(keyword: ExpressionKeyword) -> Self {
405        Self { keyword }
406    }
407
408    pub fn evaluate(&self, context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
409        match self.keyword {
410            ExpressionKeyword::StartingValue => context.starting_value,
411            ExpressionKeyword::CurrentValue => context.current_value,
412            ExpressionKeyword::FinalValue => context.final_value,
413            // should be handled by MemberAccess
414            ExpressionKeyword::Target => ExpressionVariant::Invalid,
415            ExpressionKeyword::True => true.into(),
416            ExpressionKeyword::False => false.into(),
417            ExpressionKeyword::Pi => std::f32::consts::PI.into(),
418        }
419    }
420}
421
422impl fmt::Display for KeywordExpression {
423    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
424        write!(f, "[{:?}]", self.keyword)
425    }
426}
427
428/// A unary operator applied to an expression.
429#[derive(Clone, Debug, PartialEq)]
430pub struct UnaryExpression {
431    pub parameter: Box<Expression>,
432    pub expression_type: ExpressionType,
433}
434
435impl UnaryExpression {
436    pub fn new(parameter: Expression, expression_type: ExpressionType) -> Self {
437        Self {
438            parameter: Box::new(parameter),
439            expression_type,
440        }
441    }
442
443    pub fn evaluate(&self, context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
444        match self.expression_type {
445            ExpressionType::Not => !self.parameter.evaluate(context),
446            ExpressionType::UnaryMinus => -self.parameter.evaluate(context),
447            _ => ExpressionVariant::Invalid,
448        }
449    }
450
451    pub fn collect_references(&self, references: &mut HashSet<(String, String)>) {
452        self.parameter.collect_references(references);
453    }
454}
455
456impl fmt::Display for UnaryExpression {
457    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
458        write!(f, "{}{}", Expression::operator_name(self.expression_type), self.parameter)
459    }
460}
461
462/// A binary operator applied to two expressions.
463#[derive(Clone, Debug, PartialEq)]
464pub struct BinaryExpression {
465    pub left: Box<Expression>,
466    pub right: Box<Expression>,
467    pub expression_type: ExpressionType,
468}
469
470impl BinaryExpression {
471    pub fn new(left: Expression, right: Expression, expression_type: ExpressionType) -> Self {
472        Self {
473            left: Box::new(left),
474            right: Box::new(right),
475            expression_type,
476        }
477    }
478
479    pub fn evaluate(&self, context: &ExpressionEvaluationContext<'_>) -> ExpressionVariant {
480        let left = self.left.evaluate(context);
481        let right = self.right.evaluate(context);
482        match self.expression_type {
483            ExpressionType::Add => left + right,
484            ExpressionType::Subtract => left - right,
485            ExpressionType::Multiply => left * right,
486            ExpressionType::Divide => left / right,
487            ExpressionType::Remainder => left % right,
488            ExpressionType::MoreThan => left.more_than(right),
489            ExpressionType::LessThan => left.less_than(right),
490            // The reference implementation evaluates `>=` as `>` and `<=` as `<`.
491            ExpressionType::MoreThanOrEqual => left.more_than(right),
492            ExpressionType::LessThanOrEqual => left.less_than(right),
493            ExpressionType::LogicalAnd => left.and(right),
494            ExpressionType::LogicalOr => left.or(right),
495            ExpressionType::Equals => left.equals_to(right),
496            ExpressionType::NotEquals => left.not_equals_to(right),
497            _ => ExpressionVariant::Invalid,
498        }
499    }
500
501    pub fn collect_references(&self, references: &mut HashSet<(String, String)>) {
502        self.left.collect_references(references);
503        self.right.collect_references(references);
504    }
505}
506
507impl fmt::Display for BinaryExpression {
508    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
509        write!(
510            f,
511            "({}{}{})",
512            self.left,
513            Expression::operator_name(self.expression_type),
514            self.right
515        )
516    }
517}