Skip to main content

ironcalc_base/expressions/parser/
stringify.rs

1use super::{super::utils::quote_name, Node, Reference};
2use crate::constants::{LAST_COLUMN, LAST_ROW};
3use crate::expressions::parser::move_formula::to_string_array_node;
4use crate::expressions::parser::static_analysis::remove_redundant_implicit_intersection;
5use crate::expressions::token::{OpSum, OpUnary};
6use crate::functions::Function;
7use crate::language::{get_language, Language};
8use crate::locale::{get_locale, Locale};
9use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str};
10
11pub enum DisplaceData {
12    Column {
13        sheet: u32,
14        column: i32,
15        delta: i32,
16    },
17    Row {
18        sheet: u32,
19        row: i32,
20        delta: i32,
21    },
22    CellHorizontal {
23        sheet: u32,
24        row: i32,
25        column: i32,
26        delta: i32,
27    },
28    CellVertical {
29        sheet: u32,
30        row: i32,
31        column: i32,
32        delta: i32,
33    },
34    RowMove {
35        sheet: u32,
36        row: i32,
37        delta: i32,
38    },
39    ColumnMove {
40        sheet: u32,
41        column: i32,
42        delta: i32,
43    },
44    None,
45}
46
47/// This is the internal mode in IronCalc
48/// Formulas internally are stored in R1C1 format, the locale and language are always "en"
49pub fn to_rc_format(node: &Node) -> String {
50    #[allow(clippy::expect_used)]
51    let locale = get_locale("en").expect("");
52    #[allow(clippy::expect_used)]
53    let language = get_language("en").expect("");
54    stringify(node, None, &DisplaceData::None, false, locale, language)
55}
56
57pub fn to_english_string(node: &Node, context: &CellReferenceRC) -> String {
58    #[allow(clippy::expect_used)]
59    let locale = get_locale("en").expect("");
60    #[allow(clippy::expect_used)]
61    let language = get_language("en").expect("");
62    stringify(
63        node,
64        Some(context),
65        &DisplaceData::None,
66        false,
67        locale,
68        language,
69    )
70}
71
72/// This is the mode used to display the formula in the UI
73pub fn to_localized_string(
74    node: &Node,
75    context: &CellReferenceRC,
76    locale: &Locale,
77    language: &Language,
78) -> String {
79    stringify(
80        node,
81        Some(context),
82        &DisplaceData::None,
83        false,
84        locale,
85        language,
86    )
87}
88
89/// This is the mode used to export the formula to Excel
90/// Internally the locale and language are always "en"
91pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String {
92    #[allow(clippy::expect_used)]
93    let locale = get_locale("en").expect("");
94    #[allow(clippy::expect_used)]
95    let language = get_language("en").expect("");
96    // The internal representation stores every implicit intersection as `@`,
97    // without the `automatic` flag. Drop the operators that Excel would re-insert
98    // automatically on import; the rest are kept as `_xlfn.SINGLE` while
99    // stringifying. See `remove_redundant_implicit_intersection`.
100    let mut node = node.clone();
101    remove_redundant_implicit_intersection(&mut node, true);
102    prefix_bound_variables(&mut node, &mut Vec::new());
103    stringify(
104        &node,
105        Some(context),
106        &DisplaceData::None,
107        true,
108        locale,
109        language,
110    )
111}
112
113/// Excel stores LAMBDA parameters and LET variables with an `_xlpm.` prefix,
114/// both at the declaration and at every use site:
115/// `LET(x,1,x*2)` is written as `_xlfn.LET(_xlpm.x,1,_xlpm.x*2)`.
116/// Internally IronCalc keeps the bare names, so before exporting we walk the
117/// tree tracking which names are bound by an enclosing LAMBDA/LET and rename
118/// those occurrences. Unbound names (e.g. a defined name that only resolves at
119/// evaluation time) are left alone. Matching is case-sensitive, mirroring
120/// evaluation (see `assign_variable_ids`).
121fn prefix_bound_variables(node: &mut Node, bound: &mut Vec<String>) {
122    match node {
123        Node::NamedVariableKind { name, .. } => {
124            if bound.iter().any(|n| n == name) {
125                *name = format!("_xlpm.{name}");
126            }
127        }
128        // A bound lambda used as a function: `LET(f,LAMBDA(a,a*a),f(2))`
129        Node::NamedFunctionKind { name, args, .. } => {
130            if bound.iter().any(|n| n == name) {
131                *name = format!("_xlpm.{name}");
132            }
133            for arg in args {
134                prefix_bound_variables(arg, bound);
135            }
136        }
137        Node::LambdaDefKind { parameters, body } => {
138            let depth = bound.len();
139            for parameter in parameters.iter() {
140                bound.push(parameter.name.clone());
141            }
142            prefix_bound_variables(body, bound);
143            bound.truncate(depth);
144        }
145        Node::FunctionKind {
146            kind: Function::Let,
147            args,
148        } if args.len() >= 3 && args.len() % 2 == 1 => {
149            // LET(name1, value1, [name2, value2, ...], body): each name is in
150            // scope from its own value expression onwards.
151            let depth = bound.len();
152            let pair_count = (args.len() - 1) / 2;
153            for i in 0..pair_count {
154                if let Node::NamedVariableKind { name, .. } = &mut args[2 * i] {
155                    bound.push(name.clone());
156                    *name = format!("_xlpm.{name}");
157                }
158                prefix_bound_variables(&mut args[2 * i + 1], bound);
159            }
160            prefix_bound_variables(&mut args[2 * pair_count], bound);
161            bound.truncate(depth);
162        }
163        Node::FunctionKind { args, .. } => {
164            for arg in args {
165                prefix_bound_variables(arg, bound);
166            }
167        }
168        Node::LambdaCallKind { lambda, args } => {
169            prefix_bound_variables(lambda, bound);
170            for arg in args {
171                prefix_bound_variables(arg, bound);
172            }
173        }
174        Node::OpRangeKind { left, right }
175        | Node::OpConcatenateKind { left, right }
176        | Node::OpSumKind { left, right, .. }
177        | Node::OpProductKind { left, right, .. }
178        | Node::OpPowerKind { left, right }
179        | Node::CompareKind { left, right, .. } => {
180            prefix_bound_variables(left, bound);
181            prefix_bound_variables(right, bound);
182        }
183        Node::UnaryKind { right, .. } => prefix_bound_variables(right, bound),
184        Node::ImplicitIntersection { child, .. } | Node::SpillRangeOperator { child } => {
185            prefix_bound_variables(child, bound)
186        }
187        Node::BooleanKind(_)
188        | Node::NumberKind(_)
189        | Node::StringKind(_)
190        | Node::ReferenceKind { .. }
191        | Node::RangeKind { .. }
192        | Node::WrongReferenceKind { .. }
193        | Node::WrongRangeKind { .. }
194        | Node::ArrayKind(_)
195        | Node::DefinedNameKind(_)
196        | Node::TableNameKind(_)
197        | Node::ErrorKind(_)
198        | Node::ParseErrorKind { .. }
199        | Node::EmptyArgKind => {}
200    }
201}
202
203pub fn to_string_displaced(
204    node: &Node,
205    context: &CellReferenceRC,
206    displace_data: &DisplaceData,
207    locale: &Locale,
208    language: &Language,
209) -> String {
210    stringify(node, Some(context), displace_data, false, locale, language)
211}
212
213/// Converts a local reference to a string applying some displacement if needed.
214/// It uses A1 style if context is not None. If context is None it uses R1C1 style
215/// If full_row is true then the row details will be omitted in the A1 case
216/// If full_column is true then column details will be omitted.
217pub(crate) fn stringify_reference(
218    context: Option<&CellReferenceRC>,
219    displace_data: &DisplaceData,
220    reference: &Reference,
221    full_row: bool,
222    full_column: bool,
223) -> String {
224    let sheet_name = reference.sheet_name;
225    let sheet_index = reference.sheet_index;
226    let absolute_row = reference.absolute_row;
227    let absolute_column = reference.absolute_column;
228    let row = reference.row;
229    let column = reference.column;
230    match context {
231        Some(context) => {
232            let mut row = if absolute_row { row } else { row + context.row };
233            let mut column = if absolute_column {
234                column
235            } else {
236                column + context.column
237            };
238            match displace_data {
239                DisplaceData::Row {
240                    sheet,
241                    row: displace_row,
242                    delta,
243                } => {
244                    if sheet_index == *sheet && !full_row {
245                        if *delta < 0 {
246                            if &row >= displace_row {
247                                if row < displace_row - *delta {
248                                    return "#REF!".to_string();
249                                }
250                                row += *delta;
251                            }
252                        } else if &row >= displace_row {
253                            row += *delta;
254                        }
255                    }
256                }
257                DisplaceData::Column {
258                    sheet,
259                    column: displace_column,
260                    delta,
261                } => {
262                    if sheet_index == *sheet && !full_column {
263                        if *delta < 0 {
264                            if &column >= displace_column {
265                                if column < displace_column - *delta {
266                                    return "#REF!".to_string();
267                                }
268                                column += *delta;
269                            }
270                        } else if &column >= displace_column {
271                            column += *delta;
272                        }
273                    }
274                }
275                DisplaceData::CellHorizontal {
276                    sheet,
277                    row: displace_row,
278                    column: displace_column,
279                    delta,
280                } => {
281                    if sheet_index == *sheet && displace_row == &row {
282                        if *delta < 0 {
283                            if &column >= displace_column {
284                                if column < displace_column - *delta {
285                                    return "#REF!".to_string();
286                                }
287                                column += *delta;
288                            }
289                        } else if &column >= displace_column {
290                            column += *delta;
291                        }
292                    }
293                }
294                DisplaceData::CellVertical {
295                    sheet,
296                    row: displace_row,
297                    column: displace_column,
298                    delta,
299                } => {
300                    if sheet_index == *sheet && displace_column == &column {
301                        if *delta < 0 {
302                            if &row >= displace_row {
303                                if row < displace_row - *delta {
304                                    return "#REF!".to_string();
305                                }
306                                row += *delta;
307                            }
308                        } else if &row >= displace_row {
309                            row += *delta;
310                        }
311                    }
312                }
313                DisplaceData::RowMove {
314                    sheet,
315                    row: move_row,
316                    delta,
317                } => {
318                    if sheet_index == *sheet {
319                        if row == *move_row {
320                            row += *delta;
321                        } else if *delta > 0 {
322                            // Moving the row downwards
323                            if row > *move_row && row <= *move_row + *delta {
324                                // Intermediate rows move up by one position
325                                row -= 1;
326                            }
327                        } else if *delta < 0 {
328                            // Moving the row upwards
329                            if row < *move_row && row >= *move_row + *delta {
330                                // Intermediate rows move down by one position
331                                row += 1;
332                            }
333                        }
334                    }
335                }
336                DisplaceData::ColumnMove {
337                    sheet,
338                    column: move_column,
339                    delta,
340                } => {
341                    if sheet_index == *sheet {
342                        if column == *move_column {
343                            column += *delta;
344                        } else if *delta > 0 {
345                            // Moving the column to the right
346                            if column > *move_column && column <= *move_column + *delta {
347                                // Intermediate columns move left by one position
348                                column -= 1;
349                            }
350                        } else if *delta < 0 {
351                            // Moving the column to the left
352                            if column < *move_column && column >= *move_column + *delta {
353                                // Intermediate columns move right by one position
354                                column += 1;
355                            }
356                        }
357                    }
358                }
359                DisplaceData::None => {}
360            }
361            if row < 1 {
362                return "#REF!".to_string();
363            }
364            let mut row_abs = if absolute_row {
365                format!("${row}")
366            } else {
367                format!("{row}")
368            };
369            let column = match crate::expressions::utils::number_to_column(column) {
370                Some(s) => s,
371                None => return "#REF!".to_string(),
372            };
373            let mut col_abs = if absolute_column {
374                format!("${column}")
375            } else {
376                column
377            };
378            if full_row {
379                row_abs = "".to_string()
380            }
381            if full_column {
382                col_abs = "".to_string()
383            }
384            match &sheet_name {
385                Some(name) => {
386                    format!("{}!{}{}", quote_name(name), col_abs, row_abs)
387                }
388                None => {
389                    format!("{col_abs}{row_abs}")
390                }
391            }
392        }
393        None => {
394            let row_abs = if absolute_row {
395                format!("R{row}")
396            } else {
397                format!("R[{row}]")
398            };
399            let col_abs = if absolute_column {
400                format!("C{column}")
401            } else {
402                format!("C[{column}]")
403            };
404            match &sheet_name {
405                Some(name) => {
406                    format!("{}!{}{}", quote_name(name), row_abs, col_abs)
407                }
408                None => {
409                    format!("{row_abs}{col_abs}")
410                }
411            }
412        }
413    }
414}
415
416fn format_function(
417    name: &str,
418    args: &Vec<Node>,
419    context: Option<&CellReferenceRC>,
420    displace_data: &DisplaceData,
421    export_to_excel: bool,
422    locale: &Locale,
423    language: &Language,
424) -> String {
425    let mut first = true;
426    let mut arguments = "".to_string();
427    let arg_separator = if locale.numbers.symbols.decimal == "." {
428        ','
429    } else {
430        ';'
431    };
432    for el in args {
433        if !first {
434            arguments = format!(
435                "{}{}{}",
436                arguments,
437                arg_separator,
438                stringify(
439                    el,
440                    context,
441                    displace_data,
442                    export_to_excel,
443                    locale,
444                    language
445                )
446            );
447        } else {
448            first = false;
449            arguments = stringify(
450                el,
451                context,
452                displace_data,
453                export_to_excel,
454                locale,
455                language,
456            );
457        }
458    }
459    format!("{name}({arguments})")
460}
461
462// There is just one representation in the AST (Abstract Syntax Tree) of a formula.
463// But three different ways to convert it to a string.
464//
465// To stringify a formula we need a "context", that is in which cell are we doing the "stringifying"
466//
467// But there are three ways to stringify a formula:
468//
469// * To show it to the IronCalc user
470// * To store internally
471// * To export to Excel
472//
473// There are, of course correspondingly three "modes" when parsing a formula.
474//
475// The internal representation is the more different as references are stored in the RC representation.
476// The the AST of the formula is kept close to this representation we don't need a context
477//
478// In the export to Excel representation certain things are different:
479// * We add a _xlfn. in front of some (more modern) functions
480// * We remove the Implicit Intersection operator when it is automatic and add _xlfn.SINGLE when it is not
481//
482// Examples:
483// * =A1+B2
484// * =RC+R1C1
485// * =A1+B1
486
487fn stringify(
488    node: &Node,
489    context: Option<&CellReferenceRC>,
490    displace_data: &DisplaceData,
491    export_to_excel: bool,
492    locale: &Locale,
493    language: &Language,
494) -> String {
495    use self::Node::*;
496    match node {
497        BooleanKind(value) => {
498            if *value {
499                language.booleans.r#true.to_string()
500            } else {
501                language.booleans.r#false.to_string()
502            }
503        }
504        NumberKind(number) => {
505            let s = to_excel_precision_str(*number);
506            if locale.numbers.symbols.decimal == "." {
507                s
508            } else {
509                s.replace(".", &locale.numbers.symbols.decimal)
510            }
511        }
512        StringKind(value) => format!("\"{value}\""),
513        WrongReferenceKind {
514            sheet_name,
515            column,
516            row,
517            absolute_row,
518            absolute_column,
519        } => stringify_reference(
520            context,
521            &DisplaceData::None,
522            &Reference {
523                sheet_name,
524                sheet_index: 0,
525                row: *row,
526                column: *column,
527                absolute_row: *absolute_row,
528                absolute_column: *absolute_column,
529            },
530            false,
531            false,
532        ),
533        ReferenceKind {
534            sheet_name,
535            sheet_index,
536            column,
537            row,
538            absolute_row,
539            absolute_column,
540        } => stringify_reference(
541            context,
542            displace_data,
543            &Reference {
544                sheet_name,
545                sheet_index: *sheet_index,
546                row: *row,
547                column: *column,
548                absolute_row: *absolute_row,
549                absolute_column: *absolute_column,
550            },
551            false,
552            false,
553        ),
554        RangeKind {
555            sheet_name,
556            sheet_index,
557            absolute_row1,
558            absolute_column1,
559            row1,
560            column1,
561            absolute_row2,
562            absolute_column2,
563            row2,
564            column2,
565        } => {
566            // Note that open ranges SUM(A:A) or SUM(1:1) will be treated as normal ranges in the R1C1 (internal) representation
567            // A:A will be R1C[0]:R1048576C[0]
568            // So when we are forming the A1 range we need to strip the irrelevant information
569            let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
570            let full_column = *absolute_column1
571                && *absolute_column2
572                && (*column1 == 1)
573                && (*column2 == LAST_COLUMN);
574            let s1 = stringify_reference(
575                context,
576                displace_data,
577                &Reference {
578                    sheet_name,
579                    sheet_index: *sheet_index,
580                    row: *row1,
581                    column: *column1,
582                    absolute_row: *absolute_row1,
583                    absolute_column: *absolute_column1,
584                },
585                full_row,
586                full_column,
587            );
588            let s2 = stringify_reference(
589                context,
590                displace_data,
591                &Reference {
592                    sheet_name: &None,
593                    sheet_index: *sheet_index,
594                    row: *row2,
595                    column: *column2,
596                    absolute_row: *absolute_row2,
597                    absolute_column: *absolute_column2,
598                },
599                full_row,
600                full_column,
601            );
602            format!("{s1}:{s2}")
603        }
604        WrongRangeKind {
605            sheet_name,
606            absolute_row1,
607            absolute_column1,
608            row1,
609            column1,
610            absolute_row2,
611            absolute_column2,
612            row2,
613            column2,
614        } => {
615            // Note that open ranges SUM(A:A) or SUM(1:1) will be treated as normal ranges in the R1C1 (internal) representation
616            // A:A will be R1C[0]:R1048576C[0]
617            // So when we are forming the A1 range we need to strip the irrelevant information
618            let full_row = *absolute_row1 && *absolute_row2 && (*row1 == 1) && (*row2 == LAST_ROW);
619            let full_column = *absolute_column1
620                && *absolute_column2
621                && (*column1 == 1)
622                && (*column2 == LAST_COLUMN);
623            let s1 = stringify_reference(
624                context,
625                &DisplaceData::None,
626                &Reference {
627                    sheet_name,
628                    sheet_index: 0, // HACK
629                    row: *row1,
630                    column: *column1,
631                    absolute_row: *absolute_row1,
632                    absolute_column: *absolute_column1,
633                },
634                full_row,
635                full_column,
636            );
637            let s2 = stringify_reference(
638                context,
639                &DisplaceData::None,
640                &Reference {
641                    sheet_name: &None,
642                    sheet_index: 0, // HACK
643                    row: *row2,
644                    column: *column2,
645                    absolute_row: *absolute_row2,
646                    absolute_column: *absolute_column2,
647                },
648                full_row,
649                full_column,
650            );
651            format!("{s1}:{s2}")
652        }
653        OpRangeKind { left, right } => format!(
654            "{}:{}",
655            stringify(
656                left,
657                context,
658                displace_data,
659                export_to_excel,
660                locale,
661                language
662            ),
663            stringify(
664                right,
665                context,
666                displace_data,
667                export_to_excel,
668                locale,
669                language
670            )
671        ),
672        OpConcatenateKind { left, right } => format!(
673            "{}&{}",
674            stringify(
675                left,
676                context,
677                displace_data,
678                export_to_excel,
679                locale,
680                language
681            ),
682            stringify(
683                right,
684                context,
685                displace_data,
686                export_to_excel,
687                locale,
688                language
689            )
690        ),
691        CompareKind { kind, left, right } => format!(
692            "{}{}{}",
693            stringify(
694                left,
695                context,
696                displace_data,
697                export_to_excel,
698                locale,
699                language
700            ),
701            kind,
702            stringify(
703                right,
704                context,
705                displace_data,
706                export_to_excel,
707                locale,
708                language
709            )
710        ),
711        OpSumKind { kind, left, right } => {
712            // CompareKind has lower precedence than +/-, so wrap it to preserve semantics
713            let left_str = if matches!(**left, CompareKind { .. }) {
714                format!(
715                    "({})",
716                    stringify(
717                        left,
718                        context,
719                        displace_data,
720                        export_to_excel,
721                        locale,
722                        language
723                    )
724                )
725            } else {
726                stringify(
727                    left,
728                    context,
729                    displace_data,
730                    export_to_excel,
731                    locale,
732                    language,
733                )
734            };
735            // if kind is minus then we need parentheses in the right side if they are OpSumKind or CompareKind
736            let right_str = if (matches!(kind, OpSum::Minus) && matches!(**right, OpSumKind { .. }))
737                | matches!(**right, CompareKind { .. })
738            {
739                format!(
740                    "({})",
741                    stringify(
742                        right,
743                        context,
744                        displace_data,
745                        export_to_excel,
746                        locale,
747                        language
748                    )
749                )
750            } else {
751                stringify(
752                    right,
753                    context,
754                    displace_data,
755                    export_to_excel,
756                    locale,
757                    language,
758                )
759            };
760
761            format!("{left_str}{kind}{right_str}")
762        }
763        OpProductKind { kind, left, right } => {
764            let x = match **left {
765                OpSumKind { .. } | CompareKind { .. } => format!(
766                    "({})",
767                    stringify(
768                        left,
769                        context,
770                        displace_data,
771                        export_to_excel,
772                        locale,
773                        language
774                    )
775                ),
776                _ => stringify(
777                    left,
778                    context,
779                    displace_data,
780                    export_to_excel,
781                    locale,
782                    language,
783                ),
784            };
785            let y = match **right {
786                OpSumKind { .. } | CompareKind { .. } | OpProductKind { .. } => format!(
787                    "({})",
788                    stringify(
789                        right,
790                        context,
791                        displace_data,
792                        export_to_excel,
793                        locale,
794                        language
795                    )
796                ),
797                _ => stringify(
798                    right,
799                    context,
800                    displace_data,
801                    export_to_excel,
802                    locale,
803                    language,
804                ),
805            };
806            format!("{x}{kind}{y}")
807        }
808        OpPowerKind { left, right } => {
809            let x = match **left {
810                BooleanKind(_)
811                | NumberKind(_)
812                | UnaryKind { .. }
813                | StringKind(_)
814                | ReferenceKind { .. }
815                | RangeKind { .. }
816                | WrongReferenceKind { .. }
817                | DefinedNameKind(_)
818                | TableNameKind(_)
819                | NamedVariableKind { .. }
820                | WrongRangeKind { .. } => stringify(
821                    left,
822                    context,
823                    displace_data,
824                    export_to_excel,
825                    locale,
826                    language,
827                ),
828                OpRangeKind { .. }
829                | OpConcatenateKind { .. }
830                | OpProductKind { .. }
831                | OpPowerKind { .. }
832                | FunctionKind { .. }
833                | NamedFunctionKind { .. }
834                | LambdaDefKind { .. }
835                | LambdaCallKind { .. }
836                | ArrayKind(_)
837                | ErrorKind(_)
838                | ParseErrorKind { .. }
839                | OpSumKind { .. }
840                | CompareKind { .. }
841                | ImplicitIntersection { .. }
842                | SpillRangeOperator { .. }
843                | EmptyArgKind => format!(
844                    "({})",
845                    stringify(
846                        left,
847                        context,
848                        displace_data,
849                        export_to_excel,
850                        locale,
851                        language
852                    )
853                ),
854            };
855            let y = match **right {
856                BooleanKind(_)
857                | NumberKind(_)
858                | StringKind(_)
859                | ReferenceKind { .. }
860                | RangeKind { .. }
861                | WrongReferenceKind { .. }
862                | DefinedNameKind(_)
863                | TableNameKind(_)
864                | NamedVariableKind { .. }
865                | WrongRangeKind { .. } => stringify(
866                    right,
867                    context,
868                    displace_data,
869                    export_to_excel,
870                    locale,
871                    language,
872                ),
873                OpRangeKind { .. }
874                | OpConcatenateKind { .. }
875                | OpProductKind { .. }
876                | OpPowerKind { .. }
877                | FunctionKind { .. }
878                | NamedFunctionKind { .. }
879                | LambdaDefKind { .. }
880                | LambdaCallKind { .. }
881                | ArrayKind(_)
882                | UnaryKind { .. }
883                | ErrorKind(_)
884                | ParseErrorKind { .. }
885                | OpSumKind { .. }
886                | CompareKind { .. }
887                | ImplicitIntersection { .. }
888                | SpillRangeOperator { .. }
889                | EmptyArgKind => format!(
890                    "({})",
891                    stringify(
892                        right,
893                        context,
894                        displace_data,
895                        export_to_excel,
896                        locale,
897                        language
898                    )
899                ),
900            };
901            format!("{x}^{y}")
902        }
903        NamedFunctionKind { name, args, id: _ } => format_function(
904            &name.to_lowercase(),
905            args,
906            context,
907            displace_data,
908            export_to_excel,
909            locale,
910            language,
911        ),
912        FunctionKind { kind, args } => {
913            let name = if export_to_excel {
914                kind.to_xlsx_string()
915            } else {
916                kind.to_localized_name(language)
917            };
918            format_function(
919                &name,
920                args,
921                context,
922                displace_data,
923                export_to_excel,
924                locale,
925                language,
926            )
927        }
928        ArrayKind(args) => {
929            let mut first_row = true;
930            let mut matrix_string = String::new();
931            let row_separator = if locale.numbers.symbols.decimal == "." {
932                ';'
933            } else {
934                '/'
935            };
936            let col_separator = if row_separator == ';' { ',' } else { ';' };
937
938            for row in args {
939                if !first_row {
940                    matrix_string.push(row_separator);
941                } else {
942                    first_row = false;
943                }
944                let mut first_column = true;
945                let mut row_string = String::new();
946                for el in row {
947                    if !first_column {
948                        row_string.push(col_separator);
949                    } else {
950                        first_column = false;
951                    }
952                    row_string.push_str(&to_string_array_node(el, locale, language));
953                }
954                matrix_string.push_str(&row_string);
955            }
956            format!("{{{matrix_string}}}")
957        }
958        TableNameKind(value) => value.to_string(),
959        DefinedNameKind((name, ..)) => name.to_string(),
960        NamedVariableKind { name, id: _ } => name.to_string(),
961        UnaryKind { kind, right } => match kind {
962            OpUnary::Minus => {
963                let needs_parentheses = match **right {
964                    BooleanKind(_)
965                    | NumberKind(_)
966                    | StringKind(_)
967                    | ReferenceKind { .. }
968                    | RangeKind { .. }
969                    | WrongReferenceKind { .. }
970                    | WrongRangeKind { .. }
971                    | OpRangeKind { .. }
972                    | OpConcatenateKind { .. }
973                    | OpProductKind { .. }
974                    | FunctionKind { .. }
975                    | NamedFunctionKind { .. }
976                    | LambdaDefKind { .. }
977                    | LambdaCallKind { .. }
978                    | ArrayKind(_)
979                    | DefinedNameKind(_)
980                    | TableNameKind(_)
981                    | NamedVariableKind { .. }
982                    | ImplicitIntersection { .. }
983                    | SpillRangeOperator { .. }
984                    | CompareKind { .. }
985                    | ErrorKind(_)
986                    | ParseErrorKind { .. }
987                    | EmptyArgKind => false,
988
989                    OpPowerKind { .. } | OpSumKind { .. } | UnaryKind { .. } => true,
990                };
991                if needs_parentheses {
992                    format!(
993                        "-({})",
994                        stringify(
995                            right,
996                            context,
997                            displace_data,
998                            export_to_excel,
999                            locale,
1000                            language
1001                        )
1002                    )
1003                } else {
1004                    format!(
1005                        "-{}",
1006                        stringify(
1007                            right,
1008                            context,
1009                            displace_data,
1010                            export_to_excel,
1011                            locale,
1012                            language
1013                        )
1014                    )
1015                }
1016            }
1017            OpUnary::Percentage => {
1018                format!(
1019                    "{}%",
1020                    stringify(
1021                        right,
1022                        context,
1023                        displace_data,
1024                        export_to_excel,
1025                        locale,
1026                        language
1027                    )
1028                )
1029            }
1030        },
1031        ErrorKind(kind) => format!("{kind}"),
1032        ParseErrorKind { formula, .. } => formula.to_string(),
1033        EmptyArgKind => "".to_string(),
1034        SpillRangeOperator { child } => {
1035            if export_to_excel {
1036                return format!(
1037                    "_xlfn.ANCHORARRAY({})",
1038                    stringify(
1039                        child,
1040                        context,
1041                        displace_data,
1042                        export_to_excel,
1043                        locale,
1044                        language
1045                    )
1046                );
1047            };
1048            format!(
1049                "{}#",
1050                stringify(
1051                    child,
1052                    context,
1053                    displace_data,
1054                    export_to_excel,
1055                    locale,
1056                    language
1057                )
1058            )
1059        }
1060        LambdaDefKind { parameters, body } => {
1061            let lambda_name = if export_to_excel {
1062                "_xlfn.LAMBDA"
1063            } else {
1064                "LAMBDA"
1065            };
1066            let arg_sep = if locale.numbers.symbols.decimal == "." {
1067                ","
1068            } else {
1069                ";"
1070            };
1071            let mut parts: Vec<String> = parameters
1072                .iter()
1073                .map(|p| {
1074                    if export_to_excel {
1075                        if p.is_optional {
1076                            format!("_xlop.{}", p.name)
1077                        } else {
1078                            format!("_xlpm.{}", p.name)
1079                        }
1080                    } else if p.is_optional {
1081                        format!("[{}]", p.name)
1082                    } else {
1083                        p.name.clone()
1084                    }
1085                })
1086                .collect();
1087            parts.push(stringify(
1088                body,
1089                context,
1090                displace_data,
1091                export_to_excel,
1092                locale,
1093                language,
1094            ));
1095            format!("{}({})", lambda_name, parts.join(arg_sep))
1096        }
1097        LambdaCallKind { lambda, args } => {
1098            let arg_sep = if locale.numbers.symbols.decimal == "." {
1099                ","
1100            } else {
1101                ";"
1102            };
1103            // When the callee is a plain named variable (unknown identifier used as a function),
1104            // use lowercase to distinguish from real function calls in R1C1 format and
1105            // to match the display behaviour of InvalidFunctionKind.
1106            let lambda_str = match lambda.as_ref() {
1107                Node::NamedVariableKind { name, id: _ } => name.to_lowercase(),
1108                other => stringify(
1109                    other,
1110                    context,
1111                    displace_data,
1112                    export_to_excel,
1113                    locale,
1114                    language,
1115                ),
1116            };
1117            let call_args: Vec<String> = args
1118                .iter()
1119                .map(|a| stringify(a, context, displace_data, export_to_excel, locale, language))
1120                .collect();
1121            format!("{}({})", lambda_str, call_args.join(arg_sep))
1122        }
1123        ImplicitIntersection {
1124            automatic: _,
1125            child,
1126        } => {
1127            if export_to_excel {
1128                // Redundant operators have already been stripped by
1129                // `remove_redundant_implicit_intersection`; whatever remains is
1130                // meaningful and must be exported explicitly.
1131                return format!(
1132                    "_xlfn.SINGLE({})",
1133                    stringify(
1134                        child,
1135                        context,
1136                        displace_data,
1137                        export_to_excel,
1138                        locale,
1139                        language
1140                    )
1141                );
1142            }
1143            format!(
1144                "@{}",
1145                stringify(
1146                    child,
1147                    context,
1148                    displace_data,
1149                    export_to_excel,
1150                    locale,
1151                    language
1152                )
1153            )
1154        }
1155    }
1156}
1157
1158pub(crate) fn rename_sheet_in_node(node: &mut Node, sheet_index: u32, new_name: &str) {
1159    match node {
1160        // Rename
1161        Node::ReferenceKind {
1162            sheet_name,
1163            sheet_index: index,
1164            ..
1165        } => {
1166            if *index == sheet_index && sheet_name.is_some() {
1167                *sheet_name = Some(new_name.to_owned());
1168            }
1169        }
1170        Node::RangeKind {
1171            sheet_name,
1172            sheet_index: index,
1173            ..
1174        } => {
1175            if *index == sheet_index && sheet_name.is_some() {
1176                *sheet_name = Some(new_name.to_owned());
1177            }
1178        }
1179        Node::WrongReferenceKind { sheet_name, .. } => {
1180            if let Some(name) = sheet_name {
1181                if name.to_uppercase() == new_name.to_uppercase() {
1182                    *sheet_name = Some(name.to_owned())
1183                }
1184            }
1185        }
1186        Node::WrongRangeKind { sheet_name, .. } => {
1187            if sheet_name.is_some() {
1188                *sheet_name = Some(new_name.to_owned());
1189            }
1190        }
1191
1192        // Go next level
1193        Node::OpRangeKind { left, right } => {
1194            rename_sheet_in_node(left, sheet_index, new_name);
1195            rename_sheet_in_node(right, sheet_index, new_name);
1196        }
1197        Node::OpConcatenateKind { left, right } => {
1198            rename_sheet_in_node(left, sheet_index, new_name);
1199            rename_sheet_in_node(right, sheet_index, new_name);
1200        }
1201        Node::OpSumKind {
1202            kind: _,
1203            left,
1204            right,
1205        } => {
1206            rename_sheet_in_node(left, sheet_index, new_name);
1207            rename_sheet_in_node(right, sheet_index, new_name);
1208        }
1209        Node::OpProductKind {
1210            kind: _,
1211            left,
1212            right,
1213        } => {
1214            rename_sheet_in_node(left, sheet_index, new_name);
1215            rename_sheet_in_node(right, sheet_index, new_name);
1216        }
1217        Node::OpPowerKind { left, right } => {
1218            rename_sheet_in_node(left, sheet_index, new_name);
1219            rename_sheet_in_node(right, sheet_index, new_name);
1220        }
1221        Node::FunctionKind { kind: _, args } => {
1222            for arg in args {
1223                rename_sheet_in_node(arg, sheet_index, new_name);
1224            }
1225        }
1226        Node::NamedFunctionKind {
1227            name: _,
1228            args,
1229            id: _,
1230        } => {
1231            for arg in args {
1232                rename_sheet_in_node(arg, sheet_index, new_name);
1233            }
1234        }
1235        Node::CompareKind {
1236            kind: _,
1237            left,
1238            right,
1239        } => {
1240            rename_sheet_in_node(left, sheet_index, new_name);
1241            rename_sheet_in_node(right, sheet_index, new_name);
1242        }
1243        Node::UnaryKind { kind: _, right } => {
1244            rename_sheet_in_node(right, sheet_index, new_name);
1245        }
1246        Node::ImplicitIntersection {
1247            automatic: _,
1248            child,
1249        } => {
1250            rename_sheet_in_node(child, sheet_index, new_name);
1251        }
1252        Node::SpillRangeOperator { child } => {
1253            rename_sheet_in_node(child, sheet_index, new_name);
1254        }
1255
1256        // Do nothing
1257        Node::BooleanKind(_) => {}
1258        Node::NumberKind(_) => {}
1259        Node::StringKind(_) => {}
1260        Node::ErrorKind(_) => {}
1261        Node::ParseErrorKind { .. } => {}
1262        Node::ArrayKind(_) => {}
1263        Node::DefinedNameKind(_) => {}
1264        Node::TableNameKind(_) => {}
1265        Node::NamedVariableKind { .. } => {}
1266        Node::EmptyArgKind => {}
1267        Node::LambdaDefKind {
1268            parameters: _,
1269            body,
1270        } => {
1271            rename_sheet_in_node(body, sheet_index, new_name);
1272        }
1273        Node::LambdaCallKind { lambda, args } => {
1274            rename_sheet_in_node(lambda, sheet_index, new_name);
1275            for arg in args {
1276                rename_sheet_in_node(arg, sheet_index, new_name);
1277            }
1278        }
1279    }
1280}
1281
1282pub(crate) fn rename_defined_name_in_node(
1283    node: &mut Node,
1284    name: &str,
1285    scope: Option<u32>,
1286    new_name: &str,
1287) {
1288    match node {
1289        // Rename
1290        Node::DefinedNameKind((n, s, _)) => {
1291            if name.to_lowercase() == n.to_lowercase() && *s == scope {
1292                *n = new_name.to_string();
1293            }
1294        }
1295        // Go next level
1296        Node::OpRangeKind { left, right } => {
1297            rename_defined_name_in_node(left, name, scope, new_name);
1298            rename_defined_name_in_node(right, name, scope, new_name);
1299        }
1300        Node::OpConcatenateKind { left, right } => {
1301            rename_defined_name_in_node(left, name, scope, new_name);
1302            rename_defined_name_in_node(right, name, scope, new_name);
1303        }
1304        Node::OpSumKind {
1305            kind: _,
1306            left,
1307            right,
1308        } => {
1309            rename_defined_name_in_node(left, name, scope, new_name);
1310            rename_defined_name_in_node(right, name, scope, new_name);
1311        }
1312        Node::OpProductKind {
1313            kind: _,
1314            left,
1315            right,
1316        } => {
1317            rename_defined_name_in_node(left, name, scope, new_name);
1318            rename_defined_name_in_node(right, name, scope, new_name);
1319        }
1320        Node::OpPowerKind { left, right } => {
1321            rename_defined_name_in_node(left, name, scope, new_name);
1322            rename_defined_name_in_node(right, name, scope, new_name);
1323        }
1324        Node::FunctionKind { kind: _, args } => {
1325            for arg in args {
1326                rename_defined_name_in_node(arg, name, scope, new_name);
1327            }
1328        }
1329        Node::NamedFunctionKind {
1330            name: _,
1331            args,
1332            id: _,
1333        } => {
1334            for arg in args {
1335                rename_defined_name_in_node(arg, name, scope, new_name);
1336            }
1337        }
1338        Node::CompareKind {
1339            kind: _,
1340            left,
1341            right,
1342        } => {
1343            rename_defined_name_in_node(left, name, scope, new_name);
1344            rename_defined_name_in_node(right, name, scope, new_name);
1345        }
1346        Node::UnaryKind { kind: _, right } => {
1347            rename_defined_name_in_node(right, name, scope, new_name);
1348        }
1349        Node::ImplicitIntersection {
1350            automatic: _,
1351            child,
1352        } => {
1353            rename_defined_name_in_node(child, name, scope, new_name);
1354        }
1355        Node::SpillRangeOperator { child } => {
1356            rename_defined_name_in_node(child, name, scope, new_name);
1357        }
1358        // Do nothing
1359        Node::BooleanKind(_) => {}
1360        Node::NumberKind(_) => {}
1361        Node::StringKind(_) => {}
1362        Node::ErrorKind(_) => {}
1363        Node::ParseErrorKind { .. } => {}
1364        Node::ArrayKind(_) => {}
1365        Node::EmptyArgKind => {}
1366        Node::ReferenceKind { .. } => {}
1367        Node::RangeKind { .. } => {}
1368        Node::WrongReferenceKind { .. } => {}
1369        Node::WrongRangeKind { .. } => {}
1370        Node::TableNameKind(_) => {}
1371        Node::NamedVariableKind { .. } => {}
1372        Node::LambdaDefKind {
1373            parameters: _,
1374            body,
1375        } => {
1376            rename_defined_name_in_node(body, name, scope, new_name);
1377        }
1378        Node::LambdaCallKind { lambda, args } => {
1379            rename_defined_name_in_node(lambda, name, scope, new_name);
1380            for arg in args {
1381                rename_defined_name_in_node(arg, name, scope, new_name);
1382            }
1383        }
1384    }
1385}