datex_core/fmt/
bracketing.rs

1use crate::ast::expressions::{
2    BinaryOperation, ComparisonOperation, DatexExpression, DatexExpressionData,
3    UnaryOperation,
4};
5use crate::{
6    fmt::{
7        Assoc, Format, Formatter, Operation, ParentContext,
8        options::BracketStyle,
9    },
10    global::operators::{
11        BinaryOperator, ComparisonOperator, LogicalUnaryOperator,
12        UnaryOperator,
13        binary::{ArithmeticOperator, LogicalOperator},
14    },
15};
16
17impl<'a> Formatter<'a> {
18    /// Handles bracketing of an expression based on the current formatting options.
19    pub fn handle_bracketing(
20        &'a self,
21        expression: &'a DatexExpression,
22        doc: Format<'a>,
23        parent_ctx: Option<ParentContext<'a>>,
24        is_left_child_of_parent: bool,
25    ) -> Format<'a> {
26        self.maybe_wrap_by_parent(
27            expression,
28            doc,
29            parent_ctx,
30            is_left_child_of_parent,
31        )
32    }
33
34    /// Decides whether to wrap an expression in parentheses based on its parent context.
35    pub fn maybe_wrap_by_parent(
36        &'a self,
37        expression: &'a DatexExpression,
38        inner: Format<'a>,
39        parent_ctx: Option<ParentContext<'a>>,
40        is_left_child_of_parent: bool,
41    ) -> Format<'a> {
42        // If there's no parent context, nothing forces parentheses.
43        if parent_ctx.is_none() {
44            return inner;
45        }
46
47        // handle Statements expression specially
48        if let DatexExpressionData::Statements(statements) = &expression.data {
49            println!(
50                "DEBUG: Handling Statements bracketing: {:#?}",
51                statements
52            );
53            return
54                // brackets definitely needed because multiple statements or terminated
55                if statements.statements.len() > 1 || statements.is_terminated {
56                    self.wrap_in_parens(inner)
57                }
58                // single unterminated statement - decide based on bracket style
59                else {
60                    match self.options.bracket_style {
61                        BracketStyle::KeepAll => self.wrap_in_parens(inner),
62                        BracketStyle::RemoveDuplicate | BracketStyle::Minimal => inner,
63                    }
64                };
65        }
66
67        let need = self.needs_parens_for_child_expr(
68            expression,
69            &parent_ctx.unwrap(),
70            is_left_child_of_parent,
71        );
72
73        if need {
74            self.wrap_in_parens(inner)
75        } else {
76            inner
77        }
78    }
79
80    /// Returns information about a binary operator: (precedence, associativity, is_associative)
81    pub fn binary_operator_info(
82        &self,
83        op: &BinaryOperator,
84    ) -> (u8, Assoc, bool) {
85        match op {
86            BinaryOperator::Arithmetic(aop) => match aop {
87                ArithmeticOperator::Multiply
88                | ArithmeticOperator::Divide
89                | ArithmeticOperator::Modulo => (20, Assoc::Left, false),
90                ArithmeticOperator::Add => (10, Assoc::Left, true), // + is associative
91                ArithmeticOperator::Subtract => (10, Assoc::Left, false), // - is not associative
92                ArithmeticOperator::Power => (30, Assoc::Right, false),
93                _ => (10, Assoc::Left, false),
94            },
95            BinaryOperator::Logical(lop) => match lop {
96                LogicalOperator::And => (5, Assoc::Left, false),
97                LogicalOperator::Or => (4, Assoc::Left, false),
98            },
99            // fallback
100            _ => (1, Assoc::None, false),
101        }
102    }
103
104    /// Returns information about a comparison operator: (precedence, associativity, is_associative)
105    fn comparison_operator_info(
106        &self,
107        op: &ComparisonOperator,
108    ) -> (u8, Assoc, bool) {
109        match op {
110            ComparisonOperator::Equal
111            | ComparisonOperator::NotEqual
112            | ComparisonOperator::LessThan
113            | ComparisonOperator::LessThanOrEqual
114            | ComparisonOperator::GreaterThan
115            | ComparisonOperator::GreaterThanOrEqual => (7, Assoc::None, false),
116            _ => (7, Assoc::None, false),
117        }
118    }
119
120    /// Returns information about a unary operator: (precedence, associativity, is_associative)
121    fn unary_operator_info(&self, op: &UnaryOperator) -> (u8, Assoc, bool) {
122        match op {
123            UnaryOperator::Arithmetic(_) => (35, Assoc::Right, false),
124            UnaryOperator::Logical(LogicalUnaryOperator::Not) => {
125                (35, Assoc::Right, false)
126            }
127            UnaryOperator::Reference(_) => (40, Assoc::Right, false),
128            UnaryOperator::Bitwise(_) => (35, Assoc::Right, false),
129        }
130    }
131
132    // precedence of an expression (used for children that are not binary/comparison)
133    fn expression_precedence(&self, expression: &DatexExpression) -> u8 {
134        match &expression.data {
135            DatexExpressionData::BinaryOperation(BinaryOperation {
136                operator,
137                ..
138            }) => {
139                let (prec, _, _) = self.binary_operator_info(operator);
140                prec
141            }
142            DatexExpressionData::ComparisonOperation(ComparisonOperation {
143                operator,
144                ..
145            }) => {
146                let (prec, _, _) = self.comparison_operator_info(operator);
147                prec
148            }
149            DatexExpressionData::UnaryOperation(UnaryOperation {
150                operator: op,
151                ..
152            }) => {
153                let (prec, _, _) = self.unary_operator_info(op);
154                prec
155            }
156            DatexExpressionData::CreateRef(_) => 40,
157            _ => 255, // never need parens
158        }
159    }
160
161    /// Decide if a child binary expression needs parentheses when placed under a parent operator.
162    /// `parent_prec` is precedence of parent operator, `parent_assoc` its associativity.
163    /// `is_left_child` indicates whether the child is the left operand.
164    fn needs_parens_for_child_expr(
165        &self,
166        child: &DatexExpression,
167        parent_context: &ParentContext<'a>,
168        is_left_child: bool,
169    ) -> bool {
170        // compute child's precedence (based on its expression kind)
171        let child_prec = self.expression_precedence(child);
172
173        if child_prec < parent_context.precedence {
174            return true; // child binds weaker -> parens required
175        }
176        if child_prec > parent_context.precedence {
177            return false; // child binds stronger -> safe without parens
178        }
179
180        // equal precedence, need to inspect operator identity & associativity
181        // If both child and parent are binary/comparison/unary, we can check operator identity
182        // and whether that operator is associative (so we can drop parens for same-op associative cases).
183
184        // check if same operator and is associative
185        let same_op_and_assoc = match (&child.data, &parent_context.operation) {
186            (
187                DatexExpressionData::BinaryOperation(BinaryOperation {
188                    operator,
189                    ..
190                }),
191                Operation::Binary(parent_op),
192            ) => {
193                let (_, _, c_is_assoc) = self.binary_operator_info(operator);
194                operator == *parent_op && c_is_assoc
195            }
196            (
197                DatexExpressionData::ComparisonOperation(ComparisonOperation {
198                    operator,
199                    ..
200                }),
201                Operation::Comparison(parent_op),
202            ) => {
203                let (_, _, c_is_assoc) =
204                    self.comparison_operator_info(operator);
205                operator == *parent_op && c_is_assoc
206            }
207            (
208                DatexExpressionData::UnaryOperation(UnaryOperation {
209                    operator,
210                    ..
211                }),
212                Operation::Unary(parent_op),
213            ) => {
214                let (_, _, c_is_assoc) = self.unary_operator_info(operator);
215                operator == *parent_op && c_is_assoc
216            }
217            _ => false,
218        };
219
220        if same_op_and_assoc {
221            // associative same op and precedence -> safe without parens
222            return false;
223        }
224
225        // fallback to parent associativity + which side the child is on
226        match parent_context.associativity {
227            Assoc::Left => {
228                // left-assoc: right child with equal precedence needs parens
229                !is_left_child
230            }
231            Assoc::Right => {
232                // right-assoc: left child with equal precedence needs parens
233                is_left_child
234            }
235            Assoc::None => {
236                // non-associative -> always need parens for equal-precedence children
237                true
238            }
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use crate::fmt::options::{BracketStyle, FormattingOptions};
246
247    use super::*;
248
249    fn to_string(script: &str, options: FormattingOptions) -> String {
250        let formatter = Formatter::new(script, options);
251        formatter.render()
252    }
253
254    #[test]
255    #[ignore = "bracketing must be fixed"]
256    fn bracketing() {
257        let expr = "((42))";
258        assert_eq!(
259            to_string(
260                expr,
261                FormattingOptions {
262                    bracket_style: BracketStyle::KeepAll,
263                    ..Default::default()
264                }
265            ),
266            "((42))"
267        );
268        assert_eq!(
269            to_string(
270                expr,
271                FormattingOptions {
272                    bracket_style: BracketStyle::RemoveDuplicate,
273                    ..Default::default()
274                }
275            ),
276            "(42)"
277        );
278        assert_eq!(
279            to_string(
280                expr,
281                FormattingOptions {
282                    bracket_style: BracketStyle::Minimal,
283                    ..Default::default()
284                }
285            ),
286            "42"
287        );
288    }
289
290    #[test]
291    #[ignore = "bracketing must be fixed"]
292    fn binary_operations_wrapped() {
293        // (1 + 2) * 3 requires parentheses around (1 + 2)
294        let expr = "(1 + 2) * 3 - 4 / 5";
295        assert_eq!(
296            to_string(
297                expr,
298                FormattingOptions {
299                    bracket_style: BracketStyle::Minimal,
300                    ..Default::default()
301                }
302            ),
303            "(1 + 2) * 3 - 4 / 5"
304        );
305
306        // 1 + (2 * 3) doesn't require parentheses
307        let expr = "1 + (2 * 3) - 4 / 5";
308        assert_eq!(
309            to_string(
310                expr,
311                FormattingOptions {
312                    bracket_style: BracketStyle::Minimal,
313                    ..Default::default()
314                }
315            ),
316            "1 + 2 * 3 - 4 / 5"
317        );
318    }
319
320    #[test]
321    fn associative_operations_no_parens_needed() {
322        // (1 + 2) + 3  ->  1 + 2 + 3
323        let expr = "(1 + 2) + 3";
324        assert_eq!(
325            to_string(
326                expr,
327                FormattingOptions {
328                    bracket_style: BracketStyle::Minimal,
329                    ..Default::default()
330                }
331            ),
332            "1 + 2 + 3"
333        );
334
335        // 1 + (2 + 3)  ->  1 + 2 + 3
336        let expr = "1 + (2 + 3)";
337        assert_eq!(
338            to_string(
339                expr,
340                FormattingOptions {
341                    bracket_style: BracketStyle::Minimal,
342                    ..Default::default()
343                }
344            ),
345            "1 + 2 + 3"
346        );
347    }
348
349    #[test]
350    #[ignore = "bracketing must be fixed"]
351    fn non_associative_operations_keep_parens() {
352        // 1 - (2 - 3) must keep parentheses
353        let expr = "1 - (2 - 3)";
354        assert_eq!(
355            to_string(
356                expr,
357                FormattingOptions {
358                    bracket_style: BracketStyle::Minimal,
359                    ..Default::default()
360                }
361            ),
362            "1 - (2 - 3)"
363        );
364
365        // (1 - 2) - 3 may drop parentheses
366        let expr = "(1 - 2) - 3";
367        assert_eq!(
368            to_string(
369                expr,
370                FormattingOptions {
371                    bracket_style: BracketStyle::Minimal,
372                    ..Default::default()
373                }
374            ),
375            "1 - 2 - 3"
376        );
377    }
378
379    #[test]
380    #[ignore = "bracketing must be fixed"]
381    fn power_operator_right_associative() {
382        // Power is right-associative: 2 ^ (3 ^ 4) -> no parens needed
383        let expr = "2 ^ (3 ^ 4)";
384        assert_eq!(
385            to_string(
386                expr,
387                FormattingOptions {
388                    bracket_style: BracketStyle::Minimal,
389                    ..Default::default()
390                }
391            ),
392            "2 ^ 3 ^ 4"
393        );
394
395        // (2 ^ 3) ^ 4 -> needs parens to preserve grouping
396        let expr = "(2 ^ 3) ^ 4";
397        assert_eq!(
398            to_string(
399                expr,
400                FormattingOptions {
401                    bracket_style: BracketStyle::Minimal,
402                    ..Default::default()
403                }
404            ),
405            "(2 ^ 3) ^ 4"
406        );
407    }
408
409    #[test]
410    #[ignore = "bracketing must be fixed"]
411    fn logical_and_or_precedence() {
412        // (a && b) || c -> we don't need parentheses
413        let expr = "(true and false) or true";
414        assert_eq!(
415            to_string(
416                expr,
417                FormattingOptions {
418                    bracket_style: BracketStyle::Minimal,
419                    ..Default::default()
420                }
421            ),
422            "true and false or true"
423        );
424
425        // a && (b || c) -> parentheses required
426        let expr = "true and (false or true)";
427        assert_eq!(
428            to_string(
429                expr,
430                FormattingOptions {
431                    bracket_style: BracketStyle::Minimal,
432                    ..Default::default()
433                }
434            ),
435            "true and (false or true)"
436        );
437    }
438
439    #[test]
440    #[ignore = "bracketing must be fixed"]
441    fn remove_duplicate_brackets() {
442        // (((1 + 2))) -> (1 + 2)
443        let expr = "(((1 + 2)))";
444        assert_eq!(
445            to_string(
446                expr,
447                FormattingOptions {
448                    bracket_style: BracketStyle::RemoveDuplicate,
449                    ..Default::default()
450                }
451            ),
452            "(1 + 2)"
453        );
454    }
455
456    #[test]
457    fn keep_all_brackets_exactly() {
458        // Keep exactly what the user wrote
459        let expr = "(((1 + 2)))";
460        assert_eq!(
461            to_string(
462                expr,
463                FormattingOptions {
464                    bracket_style: BracketStyle::KeepAll,
465                    ..Default::default()
466                }
467            ),
468            "(((1 + 2)))"
469        );
470    }
471
472    #[test]
473    fn minimal_vs_keepall_equivalence_for_simple() {
474        let expr = "1 + 2 * 3";
475        let minimal = to_string(
476            expr,
477            FormattingOptions {
478                bracket_style: BracketStyle::Minimal,
479                ..Default::default()
480            },
481        );
482        let keep_all = to_string(
483            expr,
484            FormattingOptions {
485                bracket_style: BracketStyle::KeepAll,
486                ..Default::default()
487            },
488        );
489        assert_eq!(minimal, keep_all);
490        assert_eq!(minimal, "1 + 2 * 3");
491    }
492}