Skip to main content

ferroui_base/rendering/composition/expressions/
expression_parser.rs

1//! The parser of composition expressions.
2
3use super::expression::{
4    BinaryExpression, ConditionalExpression, ConstantExpression, Expression, ExpressionKeyword, ExpressionKeywords,
5    ExpressionType, FunctionCallExpression, KeywordExpression, MemberAccessExpression, ParameterExpression,
6    UnaryExpression,
7};
8use super::expression_parse_exception::ExpressionParseException;
9use super::token_parser::TokenParser;
10
11/// The parser of composition expressions.
12pub struct ExpressionParser;
13
14impl ExpressionParser {
15    /// Parses an expression.
16    pub fn parse(s: &str) -> Result<Expression, ExpressionParseException> {
17        let mut p = TokenParser::new(s);
18        let (parsed, _) = parse_till_terminator(&mut p, "", false, false)?;
19        p.skip_whitespace();
20        if p.length() != 0 {
21            return Err(ExpressionParseException::new("Unexpected data ", p.position()));
22        }
23        Ok(parsed)
24    }
25}
26
27fn try_parse_atomic(parser: &mut TokenParser<'_>) -> Option<Expression> {
28    // We can parse keywords, parameter names and constants
29    let expr: Option<Expression> = if parser.try_parse_keyword_lower_case(ExpressionKeywords::STARTING_VALUE) {
30        Some(KeywordExpression::new(ExpressionKeyword::StartingValue).into())
31    } else if parser.try_parse_keyword_lower_case(ExpressionKeywords::CURRENT_VALUE) {
32        Some(KeywordExpression::new(ExpressionKeyword::CurrentValue).into())
33    } else if parser.try_parse_keyword_lower_case(ExpressionKeywords::FINAL_VALUE) {
34        Some(KeywordExpression::new(ExpressionKeyword::FinalValue).into())
35    } else if parser.try_parse_keyword_lower_case(ExpressionKeywords::PI) {
36        Some(KeywordExpression::new(ExpressionKeyword::Pi).into())
37    } else if parser.try_parse_keyword_lower_case(ExpressionKeywords::TRUE) {
38        Some(KeywordExpression::new(ExpressionKeyword::True).into())
39    } else if parser.try_parse_keyword_lower_case(ExpressionKeywords::FALSE) {
40        Some(KeywordExpression::new(ExpressionKeyword::False).into())
41    } else if parser.try_parse_keyword_lower_case(ExpressionKeywords::TARGET) {
42        Some(KeywordExpression::new(ExpressionKeyword::Target).into())
43    } else if parser.try_parse_keyword_lower_case("relativeunit.relative") {
44        Some(FunctionCallExpression::new("RelativeUnit.Relative", Vec::new()).into())
45    } else if parser.try_parse_keyword_lower_case("relativeunit.absolute") {
46        Some(FunctionCallExpression::new("RelativeUnit.Absolute", Vec::new()).into())
47    } else {
48        None
49    };
50    if expr.is_some() {
51        return expr;
52    }
53
54    if let Some(identifier) = parser.try_parse_identifier() {
55        return Some(ParameterExpression::new(identifier).into());
56    }
57
58    if let Some(scalar) = parser.try_parse_float() {
59        return Some(ConstantExpression::new(scalar).into());
60    }
61
62    None
63}
64
65fn try_parse_operator(parser: &mut TokenParser<'_>) -> Option<ExpressionType> {
66    if parser.try_consume_str("||") {
67        Some(ExpressionType::LogicalOr)
68    } else if parser.try_consume_str("&&") {
69        Some(ExpressionType::LogicalAnd)
70    } else if parser.try_consume_str(">=") {
71        Some(ExpressionType::MoreThanOrEqual)
72    } else if parser.try_consume_str("<=") {
73        Some(ExpressionType::LessThanOrEqual)
74    } else if parser.try_consume_str("==") {
75        Some(ExpressionType::Equals)
76    } else if parser.try_consume_str("!=") {
77        Some(ExpressionType::NotEquals)
78    } else {
79        Some(match parser.try_consume_any("+-/*><%")? {
80            '+' => ExpressionType::Add,
81            '-' => ExpressionType::Subtract,
82            '/' => ExpressionType::Divide,
83            '*' => ExpressionType::Multiply,
84            '<' => ExpressionType::LessThan,
85            '>' => ExpressionType::MoreThan,
86            _ => ExpressionType::Remainder,
87        })
88    }
89}
90
91/// A flat sequence of operands separated by binary operators, turned into a
92/// tree according to operator precedence.
93#[derive(Default)]
94struct ExpressionOperatorGroup {
95    expressions: Option<Vec<Option<Expression>>>,
96    operators: Vec<ExpressionType>,
97    first: Option<Expression>,
98}
99
100// https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/
101/// The operator precedence groups, from the lowest precedence to the highest.
102const OPERATOR_PRECEDENCE_GROUPS_REVERSED: [&[ExpressionType]; 6] = [
103    // conditional OR
104    &[ExpressionType::LogicalOr],
105    // conditional AND
106    &[ExpressionType::LogicalAnd],
107    // equality
108    &[ExpressionType::Equals, ExpressionType::NotEquals],
109    // relational
110    &[
111        ExpressionType::MoreThan,
112        ExpressionType::MoreThanOrEqual,
113        ExpressionType::LessThan,
114        ExpressionType::LessThanOrEqual,
115    ],
116    // additive
117    &[ExpressionType::Add, ExpressionType::Subtract],
118    // multiplicative
119    &[ExpressionType::Multiply, ExpressionType::Divide, ExpressionType::Remainder],
120];
121
122impl ExpressionOperatorGroup {
123    fn not_empty(&self) -> bool {
124        !self.empty()
125    }
126
127    fn empty(&self) -> bool {
128        self.expressions.is_none() && self.first.is_none()
129    }
130
131    fn append_first(&mut self, expr: Expression) {
132        assert!(!self.not_empty(), "the operator group already has an expression");
133        self.first = Some(expr);
134    }
135
136    fn append_with_operator(&mut self, expr: Expression, op: ExpressionType) {
137        if self.expressions.is_none() {
138            let first = self.first.take().expect("the operator group has no first expression");
139            self.expressions = Some(vec![Some(first)]);
140            self.operators = Vec::new();
141        }
142        if let Some(expressions) = &mut self.expressions {
143            expressions.push(Some(expr));
144        }
145        self.operators.push(op);
146    }
147
148    // a*b+c [a,b,c] [*,+], call with (0, 2)
149    // ToExpression(a*b) + ToExpression(c)
150    // a+b*c -> ToExpression(a) + ToExpression(b*c)
151    //
152    // The sequence is divided at the leftmost operator of the lowest
153    // precedence group present, as in the reference implementation; a run of
154    // operators of the same precedence therefore groups to the right
155    // (`a-b-c` is `a-(b-c)`).
156    fn range_to_expression(
157        expressions: &mut [Option<Expression>],
158        operators: &[ExpressionType],
159        from: usize,
160        to: usize,
161    ) -> Result<Expression, ExpressionParseException> {
162        let mut take = |index: usize| -> Result<Expression, ExpressionParseException> {
163            expressions[index]
164                .take()
165                .ok_or_else(|| ExpressionParseException::new("Expression parsing algorithm bug in ToExpression", 0))
166        };
167
168        if to - from == 0 {
169            return take(from);
170        }
171
172        if to - from == 1 {
173            let left = take(from)?;
174            let right = take(to)?;
175            return Ok(BinaryExpression::new(left, right, operators[from]).into());
176        }
177
178        for grp in OPERATOR_PRECEDENCE_GROUPS_REVERSED {
179            for c in from..to {
180                let current_operator = operators[c];
181                for &operator_from_group in grp {
182                    if current_operator == operator_from_group {
183                        // We are dividing the expression right here
184                        let left = Self::range_to_expression(expressions, operators, from, c)?;
185                        let right = Self::range_to_expression(expressions, operators, c + 1, to)?;
186                        return Ok(BinaryExpression::new(left, right, current_operator).into());
187                    }
188                }
189            }
190        }
191
192        // We shouldn't ever get here, if we are, there is something wrong in the code
193        Err(ExpressionParseException::new(
194            "Expression parsing algorithm bug in ToExpression",
195            0,
196        ))
197    }
198
199    fn into_expression(mut self) -> Result<Expression, ExpressionParseException> {
200        match &mut self.expressions {
201            None => Ok(self.first.take().expect("the operator group is empty")),
202            Some(expressions) => {
203                let to = expressions.len() - 1;
204                Self::range_to_expression(expressions, &self.operators, 0, to)
205            }
206        }
207    }
208}
209
210/// Parses an expression up to one of the terminator characters (which is
211/// consumed and returned) or up to the end of the text.
212fn parse_till_terminator(
213    parser: &mut TokenParser<'_>,
214    terminator_chars: &str,
215    throw_on_terminator: bool,
216    throw_on_end: bool,
217) -> Result<(Expression, Option<char>), ExpressionParseException> {
218    let mut left = ExpressionOperatorGroup::default();
219    loop {
220        if let Some(consumed_token) = parser.try_consume_any(terminator_chars) {
221            if throw_on_terminator || left.empty() {
222                // The reference implementation formats the (not yet assigned)
223                // output token here, so the message never names the character.
224                return Err(ExpressionParseException::new("Unexpected ''", parser.position() - 1));
225            }
226            return Ok((left.into_expression()?, Some(consumed_token)));
227        }
228        parser.skip_whitespace();
229        if parser.length() == 0 {
230            if throw_on_end || left.empty() {
231                return Err(ExpressionParseException::new(
232                    "Unexpected end of  expression",
233                    parser.position(),
234                ));
235            }
236            return Ok((left.into_expression()?, None));
237        }
238
239        let mut op: Option<ExpressionType> = None;
240        if left.not_empty() {
241            if parser.try_consume('?') {
242                let (true_part, _) = parse_till_terminator(parser, ":", false, true)?;
243                // pass through the current parsing rules to consume the rest
244                let (false_part, token) =
245                    parse_till_terminator(parser, terminator_chars, throw_on_terminator, throw_on_end)?;
246
247                return Ok((
248                    ConditionalExpression::new(left.into_expression()?, true_part, false_part).into(),
249                    token,
250                ));
251            }
252
253            // We expect a binary operator here
254            match try_parse_operator(parser) {
255                Some(sop) => op = Some(sop),
256                None => return Err(ExpressionParseException::new("Unexpected token", parser.position())),
257            }
258        }
259
260        // We expect an expression to be parsed (either due to expecting a binary operator or parsing the first part
261        let mut apply_negation = false;
262        while parser.try_consume('!') {
263            apply_negation = !apply_negation;
264        }
265
266        let mut apply_unary_minus = false;
267        while parser.try_consume('-') {
268            apply_unary_minus = !apply_unary_minus;
269        }
270
271        let mut parsed: Expression;
272
273        if parser.try_consume('(') {
274            parsed = parse_till_terminator(parser, ")", false, true)?.0;
275        } else if let Some(function_name) = parser.try_parse_call() {
276            let mut parameter_list = Vec::new();
277            while !parser.try_consume(')') {
278                let (parameter, closing_token) = parse_till_terminator(parser, ",)", false, true)?;
279                parameter_list.push(parameter);
280                if closing_token == Some(')') {
281                    break;
282                }
283                if closing_token != Some(',') {
284                    return Err(ExpressionParseException::new(
285                        "Unexpected end of the expression",
286                        parser.position(),
287                    ));
288                }
289            }
290
291            parsed = FunctionCallExpression::new(function_name, parameter_list).into();
292        } else if let Some(atomic) = try_parse_atomic(parser) {
293            parsed = atomic;
294        } else {
295            return Err(ExpressionParseException::new("Unexpected token", parser.position()));
296        }
297
298        // Parse any following member accesses
299        while parser.try_consume('.') {
300            match parser.try_parse_identifier() {
301                Some(member_name) => parsed = MemberAccessExpression::new(parsed, member_name).into(),
302                None => return Err(ExpressionParseException::new("Unexpected token", parser.position())),
303            }
304        }
305
306        // Apply ! operator
307        if apply_negation {
308            parsed = UnaryExpression::new(parsed, ExpressionType::Not).into();
309        }
310
311        if apply_unary_minus {
312            parsed = match parsed {
313                Expression::Constant(constexpr) => ConstantExpression::new(-constexpr.constant).into(),
314                other => UnaryExpression::new(other, ExpressionType::UnaryMinus).into(),
315            };
316        }
317
318        match op {
319            None => left.append_first(parsed),
320            Some(op) => left.append_with_operator(parsed, op),
321        }
322    }
323}