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