From da017b6113b33f149abfb94f5a4b603122925515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Hatcher?= Date: Wed, 20 Nov 2024 00:35:19 +0100 Subject: [PATCH] UPDATE: Implement the implicit Intersection Operator The II operator takes a range and returns a single cell that is in the same column or the same row as the present cell. This is needed for backwards compatibility with old Excel models and as a first step towards dynamic arrays. In the past Excel would evaluate `=A1:A10` in cell `C3` as `A3`, but today in results in an array containing all values in the range. To be compatible with old workbooks Excel inserts the II operator on those cases. So this PR performs an static analysis on all formulas inserting on import automatically the II operator where necessary. This we call the _automatic implicit operator_. When exporting to Excel the operator is striped away. You can also manually use the II. For instance `=SUM(@A1:A10)` in cell `C3`. This was not possible before and such a formula would break backwards compatibility with Excel. To Excel that "non automatic" form of the II is exported as `_xlfn.SINGLE()`. Th static analysis has to be done for all arithmetic operations and all functions. This is a bit of a daunting task and it is not done fully in this PR. We also need to implement arrays and dynamic arrays. My believe is that once the core operations have been implemented we can go formula by formula writing proper tests and documentation. After this PR formulas like `=A1:A10` for instance will return `#N/IMPL!` instead of performing the implicit intersection --- base/src/actions.rs | 52 +- base/src/cast.rs | 55 +- base/src/diffs.rs | 138 --- base/src/expressions/lexer/mod.rs | 1 + base/src/expressions/lexer/test/mod.rs | 1 + .../lexer/test/test_implicit_intersection.rs | 25 + base/src/expressions/parser/mod.rs | 78 +- base/src/expressions/parser/move_formula.rs | 8 +- .../src/expressions/parser/static_analysis.rs | 984 ++++++++++++++++++ base/src/expressions/parser/stringify.rs | 125 ++- base/src/expressions/parser/tests/mod.rs | 2 + .../tests/test_add_implicit_intersection.rs | 80 ++ .../tests/test_implicit_intersection.rs | 75 ++ .../parser/tests/test_move_formula.rs | 78 +- .../expressions/parser/tests/test_tables.rs | 5 +- base/src/expressions/parser/walk.rs | 278 ----- base/src/expressions/token.rs | 1 + base/src/functions/information.rs | 2 +- base/src/functions/lookup_and_reference.rs | 2 +- base/src/lib.rs | 1 - base/src/model.rs | 74 +- base/src/new_empty.rs | 7 +- base/src/test/mod.rs | 2 +- base/src/test/test_fn_concatenate.rs | 5 +- base/src/test/test_fn_formulatext.rs | 14 +- base/src/test/test_forward_references.rs | 121 --- base/src/test/test_implicit_intersection.rs | 50 + base/src/units.rs | 1 + base/src/workbook.rs | 4 +- xlsx/src/import/worksheets.rs | 15 +- xlsx/tests/test.rs | 33 +- 31 files changed, 1623 insertions(+), 694 deletions(-) delete mode 100644 base/src/diffs.rs create mode 100644 base/src/expressions/lexer/test/test_implicit_intersection.rs create mode 100644 base/src/expressions/parser/static_analysis.rs create mode 100644 base/src/expressions/parser/tests/test_add_implicit_intersection.rs create mode 100644 base/src/expressions/parser/tests/test_implicit_intersection.rs delete mode 100644 base/src/expressions/parser/walk.rs delete mode 100644 base/src/test/test_forward_references.rs create mode 100644 base/src/test/test_implicit_intersection.rs diff --git a/base/src/actions.rs b/base/src/actions.rs index 5d416ec..cbcb816 100644 --- a/base/src/actions.rs +++ b/base/src/actions.rs @@ -1,5 +1,6 @@ use crate::constants::{LAST_COLUMN, LAST_ROW}; -use crate::expressions::parser::stringify::DisplaceData; +use crate::expressions::parser::stringify::{to_string, to_string_displaced, DisplaceData}; +use crate::expressions::types::CellReferenceRC; use crate::model::Model; // NOTE: There is a difference with Excel behaviour when deleting cells/rows/columns @@ -8,16 +9,45 @@ use crate::model::Model; // I feel this is unimportant for now. impl Model { + fn shift_cell_formula( + &mut self, + sheet: u32, + row: i32, + column: i32, + displace_data: &DisplaceData, + ) -> Result<(), String> { + if let Some(f) = self + .workbook + .worksheet(sheet)? + .cell(row, column) + .and_then(|c| c.get_formula()) + { + let node = &self.parsed_formulas[sheet as usize][f as usize].clone(); + let cell_reference = CellReferenceRC { + sheet: self.workbook.worksheets[sheet as usize].get_name(), + row, + column, + }; + // FIXME: This is not a very performant way if the formula has changed :S. + let formula = to_string(node, &cell_reference); + let formula_displaced = to_string_displaced(node, &cell_reference, displace_data); + if formula != formula_displaced { + self.update_cell_with_formula(sheet, row, column, format!("={formula_displaced}"))?; + } + } + Ok(()) + } /// This function iterates over all cells in the model and shifts their formulas according to the displacement data. /// /// # Arguments /// /// * `displace_data` - A reference to `DisplaceData` describing the displacement's direction and magnitude. - fn displace_cells(&mut self, displace_data: &DisplaceData) { + fn displace_cells(&mut self, displace_data: &DisplaceData) -> Result<(), String> { let cells = self.get_all_cells(); for cell in cells { - self.shift_cell_formula(cell.index, cell.row, cell.column, displace_data); + self.shift_cell_formula(cell.index, cell.row, cell.column, displace_data)?; } + Ok(()) } /// Retrieves the column indices for a specific row in a given sheet, sorted in ascending or descending order. @@ -134,7 +164,7 @@ impl Model { column, delta: column_count, }), - ); + )?; // In the list of columns: // * Keep all the columns to the left @@ -214,7 +244,7 @@ impl Model { column, delta: -column_count, }), - ); + )?; let worksheet = &mut self.workbook.worksheet_mut(sheet)?; // deletes all the column styles @@ -338,7 +368,7 @@ impl Model { row, delta: row_count, }), - ); + )?; Ok(()) } @@ -399,7 +429,7 @@ impl Model { row, delta: -row_count, }), - ); + )?; Ok(()) } @@ -420,14 +450,14 @@ impl Model { sheet: u32, column: i32, delta: i32, - ) -> Result<(), &'static str> { + ) -> Result<(), String> { // Check boundaries let target_column = column + delta; if !(1..=LAST_COLUMN).contains(&target_column) { - return Err("Target column out of boundaries"); + return Err("Target column out of boundaries".to_string()); } if !(1..=LAST_COLUMN).contains(&column) { - return Err("Initial column out of boundaries"); + return Err("Initial column out of boundaries".to_string()); } // TODO: Add the actual displacement of data and styles @@ -439,7 +469,7 @@ impl Model { column, delta, }), - ); + )?; Ok(()) } diff --git a/base/src/cast.rs b/base/src/cast.rs index 9d0e2e4..afc6338 100644 --- a/base/src/cast.rs +++ b/base/src/cast.rs @@ -1,7 +1,6 @@ use crate::{ calc_result::{CalcResult, Range}, expressions::{parser::Node, token::Error, types::CellReferenceIndex}, - implicit_intersection::implicit_intersection, model::Model, }; @@ -39,19 +38,11 @@ impl Model { } CalcResult::EmptyCell | CalcResult::EmptyArg => Ok(0.0), error @ CalcResult::Error { .. } => Err(error), - CalcResult::Range { left, right } => { - match implicit_intersection(&cell, &Range { left, right }) { - Some(cell_reference) => { - let result = self.evaluate_cell(cell_reference); - self.cast_to_number(result, cell_reference) - } - None => Err(CalcResult::Error { - error: Error::VALUE, - origin: cell, - message: "Invalid reference (number)".to_string(), - }), - } - } + CalcResult::Range { .. } => Err(CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Arrays not supported yet".to_string(), + }), } } @@ -99,19 +90,11 @@ impl Model { } CalcResult::EmptyCell | CalcResult::EmptyArg => Ok("".to_string()), error @ CalcResult::Error { .. } => Err(error), - CalcResult::Range { left, right } => { - match implicit_intersection(&cell, &Range { left, right }) { - Some(cell_reference) => { - let result = self.evaluate_cell(cell_reference); - self.cast_to_string(result, cell_reference) - } - None => Err(CalcResult::Error { - error: Error::VALUE, - origin: cell, - message: "Invalid reference (string)".to_string(), - }), - } - } + CalcResult::Range { .. } => Err(CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Arrays not supported yet".to_string(), + }), } } @@ -151,19 +134,11 @@ impl Model { CalcResult::Boolean(b) => Ok(b), CalcResult::EmptyCell | CalcResult::EmptyArg => Ok(false), error @ CalcResult::Error { .. } => Err(error), - CalcResult::Range { left, right } => { - match implicit_intersection(&cell, &Range { left, right }) { - Some(cell_reference) => { - let result = self.evaluate_cell(cell_reference); - self.cast_to_bool(result, cell_reference) - } - None => Err(CalcResult::Error { - error: Error::VALUE, - origin: cell, - message: "Invalid reference (bool)".to_string(), - }), - } - } + CalcResult::Range { .. } => Err(CalcResult::Error { + error: Error::NIMPL, + origin: cell, + message: "Arrays not supported yet".to_string(), + }), } } diff --git a/base/src/diffs.rs b/base/src/diffs.rs deleted file mode 100644 index e02c115..0000000 --- a/base/src/diffs.rs +++ /dev/null @@ -1,138 +0,0 @@ -use crate::{ - expressions::{ - parser::{ - move_formula::ref_is_in_area, - stringify::{to_string, to_string_displaced, DisplaceData}, - walk::forward_references, - }, - types::{Area, CellReferenceIndex, CellReferenceRC}, - }, - model::Model, -}; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -#[serde(untagged, deny_unknown_fields)] -pub enum CellValue { - Value(String), - None, -} - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] -pub struct SetCellValue { - cell: CellReferenceIndex, - new_value: CellValue, - old_value: CellValue, -} - -impl Model { - #[allow(clippy::expect_used)] - pub(crate) fn shift_cell_formula( - &mut self, - sheet: u32, - row: i32, - column: i32, - displace_data: &DisplaceData, - ) { - if let Some(f) = self - .workbook - .worksheet(sheet) - .expect("Worksheet must exist") - .cell(row, column) - .expect("Cell must exist") - .get_formula() - { - let node = &self.parsed_formulas[sheet as usize][f as usize].clone(); - let cell_reference = CellReferenceRC { - sheet: self.workbook.worksheets[sheet as usize].get_name(), - row, - column, - }; - // FIXME: This is not a very performant way if the formula has changed :S. - let formula = to_string(node, &cell_reference); - let formula_displaced = to_string_displaced(node, &cell_reference, displace_data); - if formula != formula_displaced { - self.update_cell_with_formula(sheet, row, column, format!("={formula_displaced}")) - .expect("Failed to shift cell formula"); - } - } - } - - #[allow(clippy::expect_used)] - pub fn forward_references( - &mut self, - source_area: &Area, - target: &CellReferenceIndex, - ) -> Result, String> { - let mut diff_list: Vec = Vec::new(); - let target_area = &Area { - sheet: target.sheet, - row: target.row, - column: target.column, - width: source_area.width, - height: source_area.height, - }; - // Walk over every formula - let cells = self.get_all_cells(); - for cell in cells { - if let Some(f) = self - .workbook - .worksheet(cell.index) - .expect("Worksheet must exist") - .cell(cell.row, cell.column) - .expect("Cell must exist") - .get_formula() - { - let sheet = cell.index; - let row = cell.row; - let column = cell.column; - - // If cell is in the source or target area, skip - if ref_is_in_area(sheet, row, column, source_area) - || ref_is_in_area(sheet, row, column, target_area) - { - continue; - } - - // Get the formula - // Get a copy of the AST - let node = &mut self.parsed_formulas[sheet as usize][f as usize].clone(); - let cell_reference = CellReferenceRC { - sheet: self.workbook.worksheets[sheet as usize].get_name(), - column: cell.column, - row: cell.row, - }; - let context = CellReferenceIndex { sheet, column, row }; - let formula = to_string(node, &cell_reference); - let target_sheet_name = &self.workbook.worksheets[target.sheet as usize].name; - forward_references( - node, - &context, - source_area, - target.sheet, - target_sheet_name, - target.row, - target.column, - ); - - // If the string representation of the formula has changed update the cell - let updated_formula = to_string(node, &cell_reference); - if formula != updated_formula { - self.update_cell_with_formula( - sheet, - row, - column, - format!("={updated_formula}"), - )?; - // Update the diff list - diff_list.push(SetCellValue { - cell: CellReferenceIndex { sheet, column, row }, - new_value: CellValue::Value(format!("={}", updated_formula)), - old_value: CellValue::Value(format!("={}", formula)), - }); - } - } - } - Ok(diff_list) - } -} diff --git a/base/src/expressions/lexer/mod.rs b/base/src/expressions/lexer/mod.rs index 0e4dded..d629d0d 100644 --- a/base/src/expressions/lexer/mod.rs +++ b/base/src/expressions/lexer/mod.rs @@ -187,6 +187,7 @@ impl Lexer { ']' => TokenType::RightBracket, ':' => TokenType::Colon, ';' => TokenType::Semicolon, + '@' => TokenType::At, ',' => { if self.locale.numbers.symbols.decimal == "," { match self.consume_number(',') { diff --git a/base/src/expressions/lexer/test/mod.rs b/base/src/expressions/lexer/test/mod.rs index f795ba5..2da65f5 100644 --- a/base/src/expressions/lexer/test/mod.rs +++ b/base/src/expressions/lexer/test/mod.rs @@ -1,4 +1,5 @@ mod test_common; +mod test_implicit_intersection; mod test_language; mod test_locale; mod test_ranges; diff --git a/base/src/expressions/lexer/test/test_implicit_intersection.rs b/base/src/expressions/lexer/test/test_implicit_intersection.rs new file mode 100644 index 0000000..d055850 --- /dev/null +++ b/base/src/expressions/lexer/test/test_implicit_intersection.rs @@ -0,0 +1,25 @@ +#![allow(clippy::unwrap_used)] + +use crate::expressions::{ + lexer::{Lexer, LexerMode}, + token::TokenType::*, +}; +use crate::language::get_language; +use crate::locale::get_locale; + +fn new_lexer(formula: &str) -> Lexer { + let locale = get_locale("en").unwrap(); + let language = get_language("en").unwrap(); + Lexer::new(formula, LexerMode::A1, locale, language) +} + +#[test] +fn sum_implicit_intersection() { + let mut lx = new_lexer("sum(@A1:A3)"); + assert_eq!(lx.next_token(), Ident("sum".to_string())); + assert_eq!(lx.next_token(), LeftParenthesis); + assert_eq!(lx.next_token(), At); + assert!(matches!(lx.next_token(), Range { .. })); + assert_eq!(lx.next_token(), RightParenthesis); + assert_eq!(lx.next_token(), EOF); +} diff --git a/base/src/expressions/parser/mod.rs b/base/src/expressions/parser/mod.rs index 62a4d47..f284fb9 100644 --- a/base/src/expressions/parser/mod.rs +++ b/base/src/expressions/parser/mod.rs @@ -1,5 +1,5 @@ /*! -# GRAMAR +# GRAMMAR
 opComp   => '=' | '<' | '>' | '<=' } '>=' | '<>'
@@ -12,7 +12,8 @@ term    => factor (opFactor factor)*
 factor  => prod (opProd prod)*
 prod    => power ('^' power)*
 power   => (unaryOp)* range '%'*
-range   => primary (':' primary)?
+range   => implicit (':' primary)?
+implicit=> '@' primary | primary
 primary => '(' expr ')'
         => number
         => function '(' f_args ')'
@@ -45,8 +46,8 @@ use super::utils::number_to_column;
 use token::OpCompare;
 
 pub mod move_formula;
+pub mod static_analysis;
 pub mod stringify;
-pub mod walk;
 
 #[cfg(test)]
 mod tests;
@@ -81,6 +82,9 @@ fn get_table_column_by_name(table_column_name: &str, table: &Table) -> Option, String);
+
 pub(crate) struct Reference<'a> {
     sheet_name: &'a Option,
     sheet_index: u32,
@@ -164,9 +168,13 @@ pub enum Node {
         args: Vec,
     },
     ArrayKind(Vec),
-    DefinedNameKind((String, Option)),
+    DefinedNameKind(DefinedNameS),
     TableNameKind(String),
     WrongVariableKind(String),
+    ImplicitIntersection {
+        automatic: bool,
+        child: Box,
+    },
     CompareKind {
         kind: OpCompare,
         left: Box,
@@ -189,7 +197,7 @@ pub enum Node {
 pub struct Parser {
     lexer: lexer::Lexer,
     worksheets: Vec,
-    defined_names: Vec<(String, Option)>,
+    defined_names: Vec,
     context: CellReferenceRC,
     tables: HashMap,
 }
@@ -197,7 +205,7 @@ pub struct Parser {
 impl Parser {
     pub fn new(
         worksheets: Vec,
-        defined_names: Vec<(String, Option)>,
+        defined_names: Vec,
         tables: HashMap,
     ) -> Parser {
         let lexer = lexer::Lexer::new(
@@ -228,7 +236,7 @@ impl Parser {
     pub fn set_worksheets_and_names(
         &mut self,
         worksheets: Vec,
-        defined_names: Vec<(String, Option)>,
+        defined_names: Vec,
     ) {
         self.worksheets = worksheets;
         self.defined_names = defined_names;
@@ -252,17 +260,17 @@ impl Parser {
 
     // Returns:
     //  * None: If there is no defined name by that name
-    //  * Some(Some(index)): If there is a defined name local to that sheet
+    //  * Some((Some(index), formula)): If there is a defined name local to that sheet
     //  * Some(None): If there is a global defined name
-    fn get_defined_name(&self, name: &str, sheet: u32) -> Option> {
-        for (df_name, df_scope) in &self.defined_names {
+    fn get_defined_name(&self, name: &str, sheet: u32) -> Option<(Option, String)> {
+        for (df_name, df_scope, df_formula) in &self.defined_names {
             if name.to_lowercase() == df_name.to_lowercase() && df_scope == &Some(sheet) {
-                return Some(*df_scope);
+                return Some((*df_scope, df_formula.to_owned()));
             }
         }
-        for (df_name, df_scope) in &self.defined_names {
+        for (df_name, df_scope, df_formula) in &self.defined_names {
             if name.to_lowercase() == df_name.to_lowercase() && df_scope.is_none() {
-                return Some(None);
+                return Some((None, df_formula.to_owned()));
             }
         }
         None
@@ -411,7 +419,7 @@ impl Parser {
     }
 
     fn parse_range(&mut self) -> Node {
-        let t = self.parse_primary();
+        let t = self.parse_implicit();
         if let Node::ParseErrorKind { .. } = t {
             return t;
         }
@@ -430,6 +438,22 @@ impl Parser {
         t
     }
 
+    fn parse_implicit(&mut self) -> Node {
+        let next_token = self.lexer.peek_token();
+        if next_token == TokenType::At {
+            self.lexer.advance_token();
+            let t = self.parse_primary();
+            if let Node::ParseErrorKind { .. } = t {
+                return t;
+            }
+            return Node::ImplicitIntersection {
+                automatic: false,
+                child: Box::new(t),
+            };
+        }
+        self.parse_primary()
+    }
+
     fn parse_primary(&mut self) -> Node {
         let next_token = self.lexer.next_token();
         match next_token {
@@ -604,6 +628,20 @@ impl Parser {
                             args,
                         };
                     }
+                    if &name == "_xlfn.SINGLE" {
+                        if args.len() != 1 {
+                            return Node::ParseErrorKind {
+                                formula: self.lexer.get_formula(),
+                                position: self.lexer.get_position() as usize,
+                                message: "Implicit Intersection requires just one argument"
+                                    .to_string(),
+                            };
+                        }
+                        return Node::ImplicitIntersection {
+                            automatic: false,
+                            child: Box::new(args[0].clone()),
+                        };
+                    }
                     return Node::InvalidFunctionKind { name, args };
                 }
                 let context = &self.context;
@@ -620,8 +658,8 @@ impl Parser {
                 };
 
                 // Could be a defined name or a table
-                if let Some(scope) = self.get_defined_name(&name, context_sheet_index) {
-                    return Node::DefinedNameKind((name, scope));
+                if let Some((scope, formula)) = self.get_defined_name(&name, context_sheet_index) {
+                    return Node::DefinedNameKind((name, scope, formula));
                 }
                 let name_lower = name.to_lowercase();
                 for table_name in self.tables.keys() {
@@ -706,6 +744,14 @@ impl Parser {
                     message: "Unexpected token: 'POWER'".to_string(),
                 }
             }
+            TokenType::At => {
+                // A primary Node cannot start with an operator
+                Node::ParseErrorKind {
+                    formula: self.lexer.get_formula(),
+                    position: 0,
+                    message: "Unexpected token: '@'".to_string(),
+                }
+            }
             TokenType::RightParenthesis
             | TokenType::RightBracket
             | TokenType::Colon
diff --git a/base/src/expressions/parser/move_formula.rs b/base/src/expressions/parser/move_formula.rs
index 5453cb0..6b2bb1f 100644
--- a/base/src/expressions/parser/move_formula.rs
+++ b/base/src/expressions/parser/move_formula.rs
@@ -375,7 +375,7 @@ fn to_string_moved(node: &Node, move_context: &MoveContext) -> String {
             }
             format!("{{{}}}", arguments)
         }
-        DefinedNameKind((name, _)) => name.to_string(),
+        DefinedNameKind((name, ..)) => name.to_string(),
         TableNameKind(name) => name.to_string(),
         WrongVariableKind(name) => name.to_string(),
         CompareKind { kind, left, right } => format!(
@@ -395,5 +395,11 @@ fn to_string_moved(node: &Node, move_context: &MoveContext) -> String {
             position: _,
         } => formula.to_string(),
         EmptyArgKind => "".to_string(),
+        ImplicitIntersection {
+            automatic: _,
+            child,
+        } => {
+            format!("@{}", to_string_moved(child, move_context))
+        }
     }
 }
diff --git a/base/src/expressions/parser/static_analysis.rs b/base/src/expressions/parser/static_analysis.rs
new file mode 100644
index 0000000..280ac24
--- /dev/null
+++ b/base/src/expressions/parser/static_analysis.rs
@@ -0,0 +1,984 @@
+use crate::functions::Function;
+
+use super::Node;
+
+use once_cell::sync::Lazy;
+use regex::Regex;
+
+#[allow(clippy::expect_used)]
+static RE: Lazy =
+    Lazy::new(|| Regex::new(r":[A-Z]*[0-9]*$").expect("Regex is known to be valid"));
+
+fn is_range_reference(s: &str) -> bool {
+    RE.is_match(s)
+}
+
+/*
+
+# NOTES on the Implicit Intersection operator: @
+
+ Sometimes we obtain a range where we expected a single argument. This can happen:
+
+ * As an argument of a function, eg: `SIN(A1:A5)`
+ * As the result of a computation of a formula `=A1:A5`
+
+ In previous versions of the Friendly Giant the spreadsheet engine would perform an operation called _implicit intersection_
+ that tries to find a single cell within the range. It works by picking a cell in the range that is the same row or the same column
+ as the cell. If there is just one we return that otherwise we return the `#REF!` error.
+
+ Examples:
+
+ * Siting on `C3` the formula `=D1:D5` will return `D3`
+ * Sitting on `C3` the formula `=D:D` will return `D3`
+ * Sitting on `C3` the formula `=A1:A7` will return `A3`
+ * Sitting on `C3` the formula `=A5:A8` will return `#REF!`
+ * Sitting on `C3` the formula `D1:G7` will return `#REF!`
+
+ Today's version of the engine will result in a dynamic array spilling the result through several cells.
+ To force the old behaviour we can use the _implicit intersection operator_: @
+
+ * `=@A1:A7` or `=SIN(@A1:A7)
+
+ When parsing formulas that come form old workbooks this is done automatically.
+ We call this version of the II operator the _automatic_ II operator.
+
+ We can also insert the II operator in places where before was impossible:
+
+ * `=SUM(@A1:A7)`
+
+ This formulas will not be compatible with old versions of the engine. The FG will stringify this as `=SUM(_xlfn.SIMPLE(A1:A7))`.
+ */
+
+/// Transverses the formula tree adding the implicit intersection operator in all arguments of functions that
+/// expect a scalar but get a range.
+///  * A:A => @A:A
+///  * SIN(A1:D1) => SIN(@A1:D1)
+///
+/// Assumes formula return a scalar
+pub fn add_implicit_intersection(node: &mut Node, add: bool) {
+    match node {
+        Node::BooleanKind(_)
+        | Node::NumberKind(_)
+        | Node::StringKind(_)
+        | Node::ErrorKind(_)
+        | Node::EmptyArgKind
+        | Node::ParseErrorKind { .. }
+        | Node::WrongReferenceKind { .. }
+        | Node::WrongRangeKind { .. }
+        | Node::InvalidFunctionKind { .. }
+        | Node::ArrayKind(_)
+        | Node::ReferenceKind { .. } => {}
+        Node::ImplicitIntersection { child, .. } => {
+            // We need to check wether the II can be automatic or not
+            let mut new_node = child.as_ref().clone();
+            add_implicit_intersection(&mut new_node, add);
+            if matches!(&new_node, Node::ImplicitIntersection { .. }) {
+                *node = new_node
+            }
+        }
+        Node::RangeKind {
+            row1,
+            column1,
+            row2,
+            column2,
+            sheet_name,
+            sheet_index,
+            absolute_row1,
+            absolute_column1,
+            absolute_row2,
+            absolute_column2,
+        } => {
+            if add {
+                *node = Node::ImplicitIntersection {
+                    automatic: true,
+                    child: Box::new(Node::RangeKind {
+                        sheet_name: sheet_name.clone(),
+                        sheet_index: *sheet_index,
+                        absolute_row1: *absolute_row1,
+                        absolute_column1: *absolute_column1,
+                        row1: *row1,
+                        column1: *column1,
+                        absolute_row2: *absolute_row2,
+                        absolute_column2: *absolute_column2,
+                        row2: *row2,
+                        column2: *column2,
+                    }),
+                };
+            }
+        }
+        Node::OpRangeKind { left, right } => {
+            if add {
+                *node = Node::ImplicitIntersection {
+                    automatic: true,
+                    child: Box::new(Node::OpRangeKind {
+                        left: left.clone(),
+                        right: right.clone(),
+                    }),
+                }
+            }
+        }
+
+        // operations
+        Node::UnaryKind { right, .. } => add_implicit_intersection(right, add),
+        Node::OpConcatenateKind { left, right }
+        | Node::OpSumKind { left, right, .. }
+        | Node::OpProductKind { left, right, .. }
+        | Node::OpPowerKind { left, right, .. }
+        | Node::CompareKind { left, right, .. } => {
+            add_implicit_intersection(left, add);
+            add_implicit_intersection(right, add);
+        }
+
+        Node::DefinedNameKind(v) => {
+            if add {
+                // Not all defined names deserve the II operator
+                // For instance =Sheet1!A1 doesn't need to be intersected
+                if is_range_reference(&v.2) {
+                    *node = Node::ImplicitIntersection {
+                        automatic: true,
+                        child: Box::new(Node::DefinedNameKind(v.to_owned())),
+                    }
+                }
+            }
+        }
+        Node::WrongVariableKind(v) => {
+            if add {
+                *node = Node::ImplicitIntersection {
+                    automatic: true,
+                    child: Box::new(Node::WrongVariableKind(v.to_owned())),
+                }
+            }
+        }
+        Node::TableNameKind(_) => {
+            // noop for now
+        }
+        Node::FunctionKind { kind, args } => {
+            let arg_count = args.len();
+            let signature = get_function_args_signature(kind, arg_count);
+            for index in 0..arg_count {
+                if matches!(signature[index], Signature::Scalar)
+                    && matches!(
+                        run_static_analysis_on_node(&args[index]),
+                        StaticResult::Range(_, _) | StaticResult::Unknown
+                    )
+                {
+                    add_implicit_intersection(&mut args[index], true);
+                } else {
+                    add_implicit_intersection(&mut args[index], false);
+                }
+            }
+            if add
+                && matches!(
+                    run_static_analysis_on_node(node),
+                    StaticResult::Range(_, _) | StaticResult::Unknown
+                )
+            {
+                *node = Node::ImplicitIntersection {
+                    automatic: true,
+                    child: Box::new(node.clone()),
+                }
+            }
+        }
+    };
+}
+
+pub(crate) enum StaticResult {
+    Scalar,
+    Array(i32, i32),
+    Range(i32, i32),
+    Unknown,
+    // TODO: What if one of the dimensions is known?
+    // what if the dimensions are unknown but bounded?
+}
+
+fn static_analysis_op_nodes(left: &Node, right: &Node) -> StaticResult {
+    let lhs = run_static_analysis_on_node(left);
+    let rhs = run_static_analysis_on_node(right);
+    match (lhs, rhs) {
+        (StaticResult::Scalar, StaticResult::Scalar) => StaticResult::Scalar,
+        (StaticResult::Scalar, StaticResult::Array(a, b) | StaticResult::Range(a, b)) => {
+            StaticResult::Array(a, b)
+        }
+
+        (StaticResult::Array(a, b) | StaticResult::Range(a, b), StaticResult::Scalar) => {
+            StaticResult::Array(a, b)
+        }
+        (
+            StaticResult::Array(a1, b1) | StaticResult::Range(a1, b1),
+            StaticResult::Array(a2, b2) | StaticResult::Range(a2, b2),
+        ) => StaticResult::Array(a1.max(a2), b1.max(b2)),
+
+        (_, StaticResult::Unknown) => StaticResult::Unknown,
+        (StaticResult::Unknown, _) => StaticResult::Unknown,
+    }
+}
+
+// Returns:
+//  * Scalar if we can proof the result of the evaluation is a scalar
+//  * Array(a, b) if we know it will be an a x b array.
+//  * Range(a, b) if we know it will be a a x b range.
+//  * Unknown if we cannot guaranty either
+fn run_static_analysis_on_node(node: &Node) -> StaticResult {
+    match node {
+        Node::BooleanKind(_)
+        | Node::NumberKind(_)
+        | Node::StringKind(_)
+        | Node::ErrorKind(_)
+        | Node::EmptyArgKind => StaticResult::Scalar,
+        Node::UnaryKind { right, .. } => run_static_analysis_on_node(right),
+        Node::ParseErrorKind { .. } => {
+            // StaticResult::Unknown is also valid
+            StaticResult::Scalar
+        }
+        Node::WrongReferenceKind { .. } => {
+            // StaticResult::Unknown is also valid
+            StaticResult::Scalar
+        }
+        Node::WrongRangeKind { .. } => {
+            // StaticResult::Unknown or Array is also valid
+            StaticResult::Scalar
+        }
+        Node::InvalidFunctionKind { .. } => {
+            // StaticResult::Unknown is also valid
+            StaticResult::Scalar
+        }
+        Node::ArrayKind(array) => {
+            let n = array.len() as i32;
+            // FIXME: This is a placeholder until we implement arrays
+            StaticResult::Array(n, 1)
+        }
+        Node::RangeKind {
+            row1,
+            column1,
+            row2,
+            column2,
+            ..
+        } => StaticResult::Range(row2 - row1, column2 - column1),
+        Node::OpRangeKind { .. } => {
+            // TODO: We could do a bit better here
+            StaticResult::Unknown
+        }
+        Node::ReferenceKind { .. } => StaticResult::Scalar,
+
+        // binary operations
+        Node::OpConcatenateKind { left, right } => static_analysis_op_nodes(left, right),
+        Node::OpSumKind { left, right, .. } => static_analysis_op_nodes(left, right),
+        Node::OpProductKind { left, right, .. } => static_analysis_op_nodes(left, right),
+        Node::OpPowerKind { left, right, .. } => static_analysis_op_nodes(left, right),
+        Node::CompareKind { left, right, .. } => static_analysis_op_nodes(left, right),
+
+        // defined names
+        Node::DefinedNameKind(_) => StaticResult::Unknown,
+        Node::WrongVariableKind(_) => StaticResult::Unknown,
+        Node::TableNameKind(_) => StaticResult::Unknown,
+        Node::FunctionKind { kind, args } => static_analysis_on_function(kind, args),
+        Node::ImplicitIntersection { .. } => StaticResult::Scalar,
+    }
+}
+
+// If all the arguments are scalars the function will return a scalar
+// If any of the arguments is a range or an array it will return an array
+fn scalar_arguments(args: &[Node]) -> StaticResult {
+    let mut n = 0;
+    let mut m = 0;
+    for arg in args {
+        match run_static_analysis_on_node(arg) {
+            StaticResult::Scalar => {
+                // noop
+            }
+            StaticResult::Array(a, b) | StaticResult::Range(a, b) => {
+                n = n.max(a);
+                m = m.max(b);
+            }
+            StaticResult::Unknown => return StaticResult::Unknown,
+        }
+    }
+    if n == 0 && m == 0 {
+        return StaticResult::Scalar;
+    }
+    StaticResult::Array(n, m)
+}
+
+// We only care if the function can return a range or not
+fn not_implemented(_args: &[Node]) -> StaticResult {
+    StaticResult::Scalar
+}
+
+fn static_analysis_offset(args: &[Node]) -> StaticResult {
+    // If first argument is a single cell reference and there are no4th and 5th argument,
+    // or they are 1, then it is a scalar
+    let arg_count = args.len();
+    if arg_count < 3 {
+        // Actually an error
+        return StaticResult::Scalar;
+    }
+    if !matches!(args[0], Node::ReferenceKind { .. }) {
+        return StaticResult::Unknown;
+    }
+    if arg_count == 3 {
+        return StaticResult::Scalar;
+    }
+    match args[3] {
+        Node::NumberKind(f) => {
+            if f != 1.0 {
+                return StaticResult::Unknown;
+            }
+        }
+        _ => return StaticResult::Unknown,
+    };
+    if arg_count == 4 {
+        return StaticResult::Scalar;
+    }
+    match args[4] {
+        Node::NumberKind(f) => {
+            if f != 1.0 {
+                return StaticResult::Unknown;
+            }
+        }
+        _ => return StaticResult::Unknown,
+    };
+    StaticResult::Unknown
+}
+
+// fn static_analysis_choose(_args: &[Node]) -> StaticResult {
+//     // We will always insert the @ in CHOOSE, but technically it is only needed if one of the elements is a range
+//     StaticResult::Unknown
+// }
+
+fn static_analysis_indirect(_args: &[Node]) -> StaticResult {
+    // We will always insert the @, but we don't need to do that in every scenario`
+    StaticResult::Unknown
+}
+
+fn static_analysis_index(_args: &[Node]) -> StaticResult {
+    // INDEX has two forms, but they are indistinguishable at parse time.
+    StaticResult::Unknown
+}
+
+#[derive(Clone)]
+enum Signature {
+    Scalar,
+    Vector,
+    Error,
+}
+
+fn args_signature_no_args(arg_count: usize) -> Vec {
+    if arg_count == 0 {
+        vec![]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_scalars(
+    arg_count: usize,
+    required_count: usize,
+    optional_count: usize,
+) -> Vec {
+    if arg_count >= required_count && arg_count <= required_count + optional_count {
+        vec![Signature::Scalar; arg_count]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_one_vector(arg_count: usize) -> Vec {
+    if arg_count == 1 {
+        vec![Signature::Vector]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_sumif(arg_count: usize) -> Vec {
+    if arg_count == 2 {
+        vec![Signature::Vector, Signature::Scalar]
+    } else if arg_count == 3 {
+        vec![Signature::Vector, Signature::Scalar, Signature::Vector]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+// 1 or none scalars
+fn args_signature_sheet(arg_count: usize) -> Vec {
+    if arg_count == 0 {
+        vec![]
+    } else if arg_count == 1 {
+        vec![Signature::Scalar]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_hlookup(arg_count: usize) -> Vec {
+    if arg_count == 3 {
+        vec![Signature::Vector, Signature::Vector, Signature::Scalar]
+    } else if arg_count == 4 {
+        vec![
+            Signature::Vector,
+            Signature::Vector,
+            Signature::Scalar,
+            Signature::Vector,
+        ]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_index(arg_count: usize) -> Vec {
+    if arg_count == 2 {
+        vec![Signature::Vector, Signature::Scalar]
+    } else if arg_count == 3 {
+        vec![Signature::Vector, Signature::Scalar, Signature::Scalar]
+    } else if arg_count == 4 {
+        vec![
+            Signature::Vector,
+            Signature::Scalar,
+            Signature::Scalar,
+            Signature::Scalar,
+        ]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_lookup(arg_count: usize) -> Vec {
+    if arg_count == 2 {
+        vec![Signature::Vector, Signature::Vector]
+    } else if arg_count == 3 {
+        vec![Signature::Vector, Signature::Vector, Signature::Vector]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_match(arg_count: usize) -> Vec {
+    if arg_count == 2 {
+        vec![Signature::Vector, Signature::Vector]
+    } else if arg_count == 3 {
+        vec![Signature::Vector, Signature::Vector, Signature::Scalar]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_offset(arg_count: usize) -> Vec {
+    if arg_count == 3 {
+        vec![Signature::Vector, Signature::Scalar, Signature::Scalar]
+    } else if arg_count == 4 {
+        vec![
+            Signature::Vector,
+            Signature::Scalar,
+            Signature::Scalar,
+            Signature::Scalar,
+        ]
+    } else if arg_count == 5 {
+        vec![
+            Signature::Vector,
+            Signature::Scalar,
+            Signature::Scalar,
+            Signature::Scalar,
+            Signature::Scalar,
+        ]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_row(arg_count: usize) -> Vec {
+    if arg_count == 0 {
+        vec![]
+    } else if arg_count == 1 {
+        vec![Signature::Vector]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_xlookup(arg_count: usize) -> Vec {
+    if !(3..=6).contains(&arg_count) {
+        return vec![Signature::Error; arg_count];
+    }
+    let mut result = vec![Signature::Scalar; arg_count];
+    result[0] = Signature::Vector;
+    result[1] = Signature::Vector;
+    result[2] = Signature::Vector;
+    result
+}
+
+fn args_signature_textafter(arg_count: usize) -> Vec {
+    if !(2..=6).contains(&arg_count) {
+        vec![Signature::Scalar; arg_count]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_textjoin(arg_count: usize) -> Vec {
+    if arg_count >= 3 {
+        let mut result = vec![Signature::Vector; arg_count];
+        result[0] = Signature::Scalar;
+        result[1] = Signature::Scalar;
+        result
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_npv(arg_count: usize) -> Vec {
+    if arg_count < 2 {
+        return vec![Signature::Error; arg_count];
+    }
+    let mut result = vec![Signature::Vector; arg_count];
+    result[0] = Signature::Scalar;
+    result
+}
+
+fn args_signature_irr(arg_count: usize) -> Vec {
+    if arg_count > 2 {
+        vec![Signature::Error; arg_count]
+    } else if arg_count == 1 {
+        vec![Signature::Vector]
+    } else {
+        vec![Signature::Vector, Signature::Scalar]
+    }
+}
+
+fn args_signature_xirr(arg_count: usize) -> Vec {
+    if arg_count == 2 {
+        vec![Signature::Vector; arg_count]
+    } else if arg_count == 3 {
+        vec![Signature::Vector, Signature::Vector, Signature::Scalar]
+    } else {
+        vec![Signature::Error; arg_count]
+    }
+}
+
+fn args_signature_mirr(arg_count: usize) -> Vec {
+    if arg_count != 3 {
+        vec![Signature::Error; arg_count]
+    } else {
+        vec![Signature::Vector, Signature::Scalar, Signature::Scalar]
+    }
+}
+
+fn args_signature_xnpv(arg_count: usize) -> Vec {
+    if arg_count != 3 {
+        vec![Signature::Error; arg_count]
+    } else {
+        vec![Signature::Scalar, Signature::Vector, Signature::Vector]
+    }
+}
+
+// FIXME: This is terrible duplications of efforts. We use the signature in at least three different places:
+// 1. When computing the function
+// 2. Checking the arguments to see if we need to insert the implicit intersection operator
+// 3. Understanding the return value
+//
+// The signature of the functions should be defined only once
+
+// Given a function and a number of arguments this returns the arguments at each position
+// are expected to be scalars or vectors (array/ranges).
+// Sets signature::Error to all arguments if the number of arguments is incorrect.
+fn get_function_args_signature(kind: &Function, arg_count: usize) -> Vec {
+    match kind {
+        Function::And => vec![Signature::Vector; arg_count],
+        Function::False => args_signature_no_args(arg_count),
+        Function::If => args_signature_scalars(arg_count, 2, 1),
+        Function::Iferror => args_signature_scalars(arg_count, 2, 0),
+        Function::Ifna => args_signature_scalars(arg_count, 2, 0),
+        Function::Ifs => vec![Signature::Scalar; arg_count],
+        Function::Not => args_signature_scalars(arg_count, 1, 0),
+        Function::Or => vec![Signature::Vector; arg_count],
+        Function::Switch => vec![Signature::Scalar; arg_count],
+        Function::True => args_signature_no_args(arg_count),
+        Function::Xor => vec![Signature::Vector; arg_count],
+        Function::Abs => args_signature_scalars(arg_count, 1, 0),
+        Function::Acos => args_signature_scalars(arg_count, 1, 0),
+        Function::Acosh => args_signature_scalars(arg_count, 1, 0),
+        Function::Asin => args_signature_scalars(arg_count, 1, 0),
+        Function::Asinh => args_signature_scalars(arg_count, 1, 0),
+        Function::Atan => args_signature_scalars(arg_count, 1, 0),
+        Function::Atan2 => args_signature_scalars(arg_count, 2, 0),
+        Function::Atanh => args_signature_scalars(arg_count, 1, 0),
+        Function::Choose => vec![Signature::Scalar; arg_count],
+        Function::Column => args_signature_row(arg_count),
+        Function::Columns => args_signature_one_vector(arg_count),
+        Function::Cos => args_signature_scalars(arg_count, 1, 0),
+        Function::Cosh => args_signature_scalars(arg_count, 1, 0),
+        Function::Max => vec![Signature::Vector; arg_count],
+        Function::Min => vec![Signature::Vector; arg_count],
+        Function::Pi => args_signature_no_args(arg_count),
+        Function::Power => args_signature_scalars(arg_count, 2, 0),
+        Function::Product => vec![Signature::Vector; arg_count],
+        Function::Round => args_signature_scalars(arg_count, 2, 0),
+        Function::Rounddown => args_signature_scalars(arg_count, 2, 0),
+        Function::Roundup => args_signature_scalars(arg_count, 2, 0),
+        Function::Sin => args_signature_scalars(arg_count, 1, 0),
+        Function::Sinh => args_signature_scalars(arg_count, 1, 0),
+        Function::Sqrt => args_signature_scalars(arg_count, 1, 0),
+        Function::Sqrtpi => args_signature_scalars(arg_count, 1, 0),
+        Function::Sum => vec![Signature::Vector; arg_count],
+        Function::Sumif => args_signature_sumif(arg_count),
+        Function::Sumifs => vec![Signature::Vector; arg_count],
+        Function::Tan => args_signature_scalars(arg_count, 1, 0),
+        Function::Tanh => args_signature_scalars(arg_count, 1, 0),
+        Function::ErrorType => args_signature_scalars(arg_count, 1, 0),
+        Function::Isblank => args_signature_scalars(arg_count, 1, 0),
+        Function::Iserr => args_signature_scalars(arg_count, 1, 0),
+        Function::Iserror => args_signature_scalars(arg_count, 1, 0),
+        Function::Iseven => args_signature_scalars(arg_count, 1, 0),
+        Function::Isformula => args_signature_scalars(arg_count, 1, 0),
+        Function::Islogical => args_signature_scalars(arg_count, 1, 0),
+        Function::Isna => args_signature_scalars(arg_count, 1, 0),
+        Function::Isnontext => args_signature_scalars(arg_count, 1, 0),
+        Function::Isnumber => args_signature_scalars(arg_count, 1, 0),
+        Function::Isodd => args_signature_scalars(arg_count, 1, 0),
+        Function::Isref => args_signature_one_vector(arg_count),
+        Function::Istext => args_signature_scalars(arg_count, 1, 0),
+        Function::Na => args_signature_no_args(arg_count),
+        Function::Sheet => args_signature_sheet(arg_count),
+        Function::Type => args_signature_one_vector(arg_count),
+        Function::Hlookup => args_signature_hlookup(arg_count),
+        Function::Index => args_signature_index(arg_count),
+        Function::Indirect => args_signature_scalars(arg_count, 1, 0),
+        Function::Lookup => args_signature_lookup(arg_count),
+        Function::Match => args_signature_match(arg_count),
+        Function::Offset => args_signature_offset(arg_count),
+        Function::Row => args_signature_row(arg_count),
+        Function::Rows => args_signature_one_vector(arg_count),
+        Function::Vlookup => args_signature_hlookup(arg_count),
+        Function::Xlookup => args_signature_xlookup(arg_count),
+        Function::Concat => vec![Signature::Vector; arg_count],
+        Function::Concatenate => vec![Signature::Scalar; arg_count],
+        Function::Exact => args_signature_scalars(arg_count, 2, 0),
+        Function::Find => args_signature_scalars(arg_count, 2, 1),
+        Function::Left => args_signature_scalars(arg_count, 1, 1),
+        Function::Len => args_signature_scalars(arg_count, 1, 0),
+        Function::Lower => args_signature_scalars(arg_count, 1, 0),
+        Function::Mid => args_signature_scalars(arg_count, 3, 0),
+        Function::Rept => args_signature_scalars(arg_count, 2, 0),
+        Function::Right => args_signature_scalars(arg_count, 2, 1),
+        Function::Search => args_signature_scalars(arg_count, 2, 1),
+        Function::Substitute => args_signature_scalars(arg_count, 3, 1),
+        Function::T => args_signature_scalars(arg_count, 1, 0),
+        Function::Text => args_signature_scalars(arg_count, 2, 0),
+        Function::Textafter => args_signature_textafter(arg_count),
+        Function::Textbefore => args_signature_textafter(arg_count),
+        Function::Textjoin => args_signature_textjoin(arg_count),
+        Function::Trim => args_signature_scalars(arg_count, 1, 0),
+        Function::Upper => args_signature_scalars(arg_count, 1, 0),
+        Function::Value => args_signature_scalars(arg_count, 1, 0),
+        Function::Valuetotext => args_signature_scalars(arg_count, 1, 1),
+        Function::Average => vec![Signature::Vector; arg_count],
+        Function::Averagea => vec![Signature::Vector; arg_count],
+        Function::Averageif => args_signature_sumif(arg_count),
+        Function::Averageifs => vec![Signature::Vector; arg_count],
+        Function::Count => vec![Signature::Vector; arg_count],
+        Function::Counta => vec![Signature::Vector; arg_count],
+        Function::Countblank => vec![Signature::Vector; arg_count],
+        Function::Countif => args_signature_sumif(arg_count),
+        Function::Countifs => vec![Signature::Vector; arg_count],
+        Function::Maxifs => vec![Signature::Vector; arg_count],
+        Function::Minifs => vec![Signature::Vector; arg_count],
+        Function::Date => args_signature_scalars(arg_count, 3, 0),
+        Function::Day => args_signature_scalars(arg_count, 1, 0),
+        Function::Edate => args_signature_scalars(arg_count, 2, 0),
+        Function::Eomonth => args_signature_scalars(arg_count, 2, 0),
+        Function::Month => args_signature_scalars(arg_count, 1, 0),
+        Function::Now => args_signature_no_args(arg_count),
+        Function::Today => args_signature_no_args(arg_count),
+        Function::Year => args_signature_scalars(arg_count, 1, 0),
+        Function::Cumipmt => args_signature_scalars(arg_count, 6, 0),
+        Function::Cumprinc => args_signature_scalars(arg_count, 6, 0),
+        Function::Db => args_signature_scalars(arg_count, 4, 1),
+        Function::Ddb => args_signature_scalars(arg_count, 4, 1),
+        Function::Dollarde => args_signature_scalars(arg_count, 2, 0),
+        Function::Dollarfr => args_signature_scalars(arg_count, 2, 0),
+        Function::Effect => args_signature_scalars(arg_count, 2, 0),
+        Function::Fv => args_signature_scalars(arg_count, 3, 2),
+        Function::Ipmt => args_signature_scalars(arg_count, 4, 2),
+        Function::Irr => args_signature_irr(arg_count),
+        Function::Ispmt => args_signature_scalars(arg_count, 4, 0),
+        Function::Mirr => args_signature_mirr(arg_count),
+        Function::Nominal => args_signature_scalars(arg_count, 2, 0),
+        Function::Nper => args_signature_scalars(arg_count, 3, 2),
+        Function::Npv => args_signature_npv(arg_count),
+        Function::Pduration => args_signature_scalars(arg_count, 3, 0),
+        Function::Pmt => args_signature_scalars(arg_count, 3, 2),
+        Function::Ppmt => args_signature_scalars(arg_count, 4, 2),
+        Function::Pv => args_signature_scalars(arg_count, 3, 2),
+        Function::Rate => args_signature_scalars(arg_count, 3, 3),
+        Function::Rri => args_signature_scalars(arg_count, 3, 0),
+        Function::Sln => args_signature_scalars(arg_count, 3, 0),
+        Function::Syd => args_signature_scalars(arg_count, 4, 0),
+        Function::Tbilleq => args_signature_scalars(arg_count, 3, 0),
+        Function::Tbillprice => args_signature_scalars(arg_count, 3, 0),
+        Function::Tbillyield => args_signature_scalars(arg_count, 3, 0),
+        Function::Xirr => args_signature_xirr(arg_count),
+        Function::Xnpv => args_signature_xnpv(arg_count),
+        Function::Besseli => args_signature_scalars(arg_count, 2, 0),
+        Function::Besselj => args_signature_scalars(arg_count, 2, 0),
+        Function::Besselk => args_signature_scalars(arg_count, 2, 0),
+        Function::Bessely => args_signature_scalars(arg_count, 2, 0),
+        Function::Erf => args_signature_scalars(arg_count, 1, 1),
+        Function::Erfc => args_signature_scalars(arg_count, 1, 0),
+        Function::ErfcPrecise => args_signature_scalars(arg_count, 1, 0),
+        Function::ErfPrecise => args_signature_scalars(arg_count, 1, 0),
+        Function::Bin2dec => args_signature_scalars(arg_count, 1, 0),
+        Function::Bin2hex => args_signature_scalars(arg_count, 1, 0),
+        Function::Bin2oct => args_signature_scalars(arg_count, 1, 0),
+        Function::Dec2Bin => args_signature_scalars(arg_count, 1, 0),
+        Function::Dec2hex => args_signature_scalars(arg_count, 1, 0),
+        Function::Dec2oct => args_signature_scalars(arg_count, 1, 0),
+        Function::Hex2bin => args_signature_scalars(arg_count, 1, 0),
+        Function::Hex2dec => args_signature_scalars(arg_count, 1, 0),
+        Function::Hex2oct => args_signature_scalars(arg_count, 1, 0),
+        Function::Oct2bin => args_signature_scalars(arg_count, 1, 0),
+        Function::Oct2dec => args_signature_scalars(arg_count, 1, 0),
+        Function::Oct2hex => args_signature_scalars(arg_count, 1, 0),
+        Function::Bitand => args_signature_scalars(arg_count, 2, 0),
+        Function::Bitlshift => args_signature_scalars(arg_count, 2, 0),
+        Function::Bitor => args_signature_scalars(arg_count, 2, 0),
+        Function::Bitrshift => args_signature_scalars(arg_count, 2, 0),
+        Function::Bitxor => args_signature_scalars(arg_count, 2, 0),
+        Function::Complex => args_signature_scalars(arg_count, 2, 1),
+        Function::Imabs => args_signature_scalars(arg_count, 1, 0),
+        Function::Imaginary => args_signature_scalars(arg_count, 1, 0),
+        Function::Imargument => args_signature_scalars(arg_count, 1, 0),
+        Function::Imconjugate => args_signature_scalars(arg_count, 1, 0),
+        Function::Imcos => args_signature_scalars(arg_count, 1, 0),
+        Function::Imcosh => args_signature_scalars(arg_count, 1, 0),
+        Function::Imcot => args_signature_scalars(arg_count, 1, 0),
+        Function::Imcsc => args_signature_scalars(arg_count, 1, 0),
+        Function::Imcsch => args_signature_scalars(arg_count, 1, 0),
+        Function::Imdiv => args_signature_scalars(arg_count, 2, 0),
+        Function::Imexp => args_signature_scalars(arg_count, 1, 0),
+        Function::Imln => args_signature_scalars(arg_count, 1, 0),
+        Function::Imlog10 => args_signature_scalars(arg_count, 1, 0),
+        Function::Imlog2 => args_signature_scalars(arg_count, 1, 0),
+        Function::Impower => args_signature_scalars(arg_count, 2, 0),
+        Function::Improduct => args_signature_scalars(arg_count, 2, 0),
+        Function::Imreal => args_signature_scalars(arg_count, 1, 0),
+        Function::Imsec => args_signature_scalars(arg_count, 1, 0),
+        Function::Imsech => args_signature_scalars(arg_count, 1, 0),
+        Function::Imsin => args_signature_scalars(arg_count, 1, 0),
+        Function::Imsinh => args_signature_scalars(arg_count, 1, 0),
+        Function::Imsqrt => args_signature_scalars(arg_count, 1, 0),
+        Function::Imsub => args_signature_scalars(arg_count, 2, 0),
+        Function::Imsum => args_signature_scalars(arg_count, 2, 0),
+        Function::Imtan => args_signature_scalars(arg_count, 1, 0),
+        Function::Convert => args_signature_scalars(arg_count, 3, 0),
+        Function::Delta => args_signature_scalars(arg_count, 1, 1),
+        Function::Gestep => args_signature_scalars(arg_count, 1, 1),
+        Function::Subtotal => args_signature_npv(arg_count),
+        Function::Rand => args_signature_no_args(arg_count),
+        Function::Randbetween => args_signature_scalars(arg_count, 2, 0),
+        Function::Formulatext => args_signature_scalars(arg_count, 1, 0),
+        Function::Unicode => args_signature_scalars(arg_count, 1, 0),
+        Function::Geomean => vec![Signature::Vector; arg_count],
+    }
+}
+
+// Returns the type of the result (Scalar, Array or Range) depending on the arguments
+fn static_analysis_on_function(kind: &Function, args: &[Node]) -> StaticResult {
+    match kind {
+        Function::And => StaticResult::Scalar,
+        Function::False => StaticResult::Scalar,
+        Function::If => scalar_arguments(args),
+        Function::Iferror => scalar_arguments(args),
+        Function::Ifna => scalar_arguments(args),
+        Function::Ifs => not_implemented(args),
+        Function::Not => StaticResult::Scalar,
+        Function::Or => StaticResult::Scalar,
+        Function::Switch => not_implemented(args),
+        Function::True => StaticResult::Scalar,
+        Function::Xor => StaticResult::Scalar,
+        Function::Abs => scalar_arguments(args),
+        Function::Acos => scalar_arguments(args),
+        Function::Acosh => scalar_arguments(args),
+        Function::Asin => scalar_arguments(args),
+        Function::Asinh => scalar_arguments(args),
+        Function::Atan => scalar_arguments(args),
+        Function::Atan2 => scalar_arguments(args),
+        Function::Atanh => scalar_arguments(args),
+        Function::Choose => scalar_arguments(args), // static_analysis_choose(args, cell),
+        Function::Column => not_implemented(args),
+        Function::Columns => not_implemented(args),
+        Function::Cos => scalar_arguments(args),
+        Function::Cosh => scalar_arguments(args),
+        Function::Max => StaticResult::Scalar,
+        Function::Min => StaticResult::Scalar,
+        Function::Pi => StaticResult::Scalar,
+        Function::Power => scalar_arguments(args),
+        Function::Product => not_implemented(args),
+        Function::Round => scalar_arguments(args),
+        Function::Rounddown => scalar_arguments(args),
+        Function::Roundup => scalar_arguments(args),
+        Function::Sin => scalar_arguments(args),
+        Function::Sinh => scalar_arguments(args),
+        Function::Sqrt => scalar_arguments(args),
+        Function::Sqrtpi => StaticResult::Scalar,
+        Function::Sum => StaticResult::Scalar,
+        Function::Sumif => not_implemented(args),
+        Function::Sumifs => not_implemented(args),
+        Function::Tan => scalar_arguments(args),
+        Function::Tanh => scalar_arguments(args),
+        Function::ErrorType => not_implemented(args),
+        Function::Isblank => not_implemented(args),
+        Function::Iserr => not_implemented(args),
+        Function::Iserror => not_implemented(args),
+        Function::Iseven => not_implemented(args),
+        Function::Isformula => not_implemented(args),
+        Function::Islogical => not_implemented(args),
+        Function::Isna => not_implemented(args),
+        Function::Isnontext => not_implemented(args),
+        Function::Isnumber => not_implemented(args),
+        Function::Isodd => not_implemented(args),
+        Function::Isref => not_implemented(args),
+        Function::Istext => not_implemented(args),
+        Function::Na => StaticResult::Scalar,
+        Function::Sheet => StaticResult::Scalar,
+        Function::Type => not_implemented(args),
+        Function::Hlookup => not_implemented(args),
+        Function::Index => static_analysis_index(args),
+        Function::Indirect => static_analysis_indirect(args),
+        Function::Lookup => not_implemented(args),
+        Function::Match => not_implemented(args),
+        Function::Offset => static_analysis_offset(args),
+        // FIXME: Row could return an array
+        Function::Row => StaticResult::Scalar,
+        Function::Rows => not_implemented(args),
+        Function::Vlookup => not_implemented(args),
+        Function::Xlookup => not_implemented(args),
+        Function::Concat => not_implemented(args),
+        Function::Concatenate => not_implemented(args),
+        Function::Exact => not_implemented(args),
+        Function::Find => not_implemented(args),
+        Function::Left => not_implemented(args),
+        Function::Len => not_implemented(args),
+        Function::Lower => not_implemented(args),
+        Function::Mid => not_implemented(args),
+        Function::Rept => not_implemented(args),
+        Function::Right => not_implemented(args),
+        Function::Search => not_implemented(args),
+        Function::Substitute => not_implemented(args),
+        Function::T => not_implemented(args),
+        Function::Text => not_implemented(args),
+        Function::Textafter => not_implemented(args),
+        Function::Textbefore => not_implemented(args),
+        Function::Textjoin => not_implemented(args),
+        Function::Trim => not_implemented(args),
+        Function::Unicode => not_implemented(args),
+        Function::Upper => not_implemented(args),
+        Function::Value => not_implemented(args),
+        Function::Valuetotext => not_implemented(args),
+        Function::Average => not_implemented(args),
+        Function::Averagea => not_implemented(args),
+        Function::Averageif => not_implemented(args),
+        Function::Averageifs => not_implemented(args),
+        Function::Count => not_implemented(args),
+        Function::Counta => not_implemented(args),
+        Function::Countblank => not_implemented(args),
+        Function::Countif => not_implemented(args),
+        Function::Countifs => not_implemented(args),
+        Function::Maxifs => not_implemented(args),
+        Function::Minifs => not_implemented(args),
+        Function::Date => not_implemented(args),
+        Function::Day => not_implemented(args),
+        Function::Edate => not_implemented(args),
+        Function::Month => not_implemented(args),
+        Function::Now => not_implemented(args),
+        Function::Today => not_implemented(args),
+        Function::Year => not_implemented(args),
+        Function::Cumipmt => not_implemented(args),
+        Function::Cumprinc => not_implemented(args),
+        Function::Db => not_implemented(args),
+        Function::Ddb => not_implemented(args),
+        Function::Dollarde => not_implemented(args),
+        Function::Dollarfr => not_implemented(args),
+        Function::Effect => not_implemented(args),
+        Function::Fv => not_implemented(args),
+        Function::Ipmt => not_implemented(args),
+        Function::Irr => not_implemented(args),
+        Function::Ispmt => not_implemented(args),
+        Function::Mirr => not_implemented(args),
+        Function::Nominal => not_implemented(args),
+        Function::Nper => not_implemented(args),
+        Function::Npv => not_implemented(args),
+        Function::Pduration => not_implemented(args),
+        Function::Pmt => not_implemented(args),
+        Function::Ppmt => not_implemented(args),
+        Function::Pv => not_implemented(args),
+        Function::Rate => not_implemented(args),
+        Function::Rri => not_implemented(args),
+        Function::Sln => not_implemented(args),
+        Function::Syd => not_implemented(args),
+        Function::Tbilleq => not_implemented(args),
+        Function::Tbillprice => not_implemented(args),
+        Function::Tbillyield => not_implemented(args),
+        Function::Xirr => not_implemented(args),
+        Function::Xnpv => not_implemented(args),
+        Function::Besseli => scalar_arguments(args),
+        Function::Besselj => scalar_arguments(args),
+        Function::Besselk => scalar_arguments(args),
+        Function::Bessely => scalar_arguments(args),
+        Function::Erf => scalar_arguments(args),
+        Function::Erfc => scalar_arguments(args),
+        Function::ErfcPrecise => scalar_arguments(args),
+        Function::ErfPrecise => scalar_arguments(args),
+        Function::Bin2dec => scalar_arguments(args),
+        Function::Bin2hex => scalar_arguments(args),
+        Function::Bin2oct => scalar_arguments(args),
+        Function::Dec2Bin => scalar_arguments(args),
+        Function::Dec2hex => scalar_arguments(args),
+        Function::Dec2oct => scalar_arguments(args),
+        Function::Hex2bin => scalar_arguments(args),
+        Function::Hex2dec => scalar_arguments(args),
+        Function::Hex2oct => scalar_arguments(args),
+        Function::Oct2bin => scalar_arguments(args),
+        Function::Oct2dec => scalar_arguments(args),
+        Function::Oct2hex => scalar_arguments(args),
+        Function::Bitand => scalar_arguments(args),
+        Function::Bitlshift => scalar_arguments(args),
+        Function::Bitor => scalar_arguments(args),
+        Function::Bitrshift => scalar_arguments(args),
+        Function::Bitxor => scalar_arguments(args),
+        Function::Complex => scalar_arguments(args),
+        Function::Imabs => scalar_arguments(args),
+        Function::Imaginary => scalar_arguments(args),
+        Function::Imargument => scalar_arguments(args),
+        Function::Imconjugate => scalar_arguments(args),
+        Function::Imcos => scalar_arguments(args),
+        Function::Imcosh => scalar_arguments(args),
+        Function::Imcot => scalar_arguments(args),
+        Function::Imcsc => scalar_arguments(args),
+        Function::Imcsch => scalar_arguments(args),
+        Function::Imdiv => scalar_arguments(args),
+        Function::Imexp => scalar_arguments(args),
+        Function::Imln => scalar_arguments(args),
+        Function::Imlog10 => scalar_arguments(args),
+        Function::Imlog2 => scalar_arguments(args),
+        Function::Impower => scalar_arguments(args),
+        Function::Improduct => scalar_arguments(args),
+        Function::Imreal => scalar_arguments(args),
+        Function::Imsec => scalar_arguments(args),
+        Function::Imsech => scalar_arguments(args),
+        Function::Imsin => scalar_arguments(args),
+        Function::Imsinh => scalar_arguments(args),
+        Function::Imsqrt => scalar_arguments(args),
+        Function::Imsub => scalar_arguments(args),
+        Function::Imsum => scalar_arguments(args),
+        Function::Imtan => scalar_arguments(args),
+        Function::Convert => not_implemented(args),
+        Function::Delta => not_implemented(args),
+        Function::Gestep => not_implemented(args),
+        Function::Subtotal => not_implemented(args),
+        Function::Rand => not_implemented(args),
+        Function::Randbetween => scalar_arguments(args),
+        Function::Eomonth => scalar_arguments(args),
+        Function::Formulatext => not_implemented(args),
+        Function::Geomean => not_implemented(args),
+    }
+}
diff --git a/base/src/expressions/parser/stringify.rs b/base/src/expressions/parser/stringify.rs
index 00f2d5f..0b87865 100644
--- a/base/src/expressions/parser/stringify.rs
+++ b/base/src/expressions/parser/stringify.rs
@@ -1,5 +1,6 @@
 use super::{super::utils::quote_name, Node, Reference};
 use crate::constants::{LAST_COLUMN, LAST_ROW};
+use crate::expressions::parser::static_analysis::add_implicit_intersection;
 use crate::expressions::token::OpUnary;
 use crate::{expressions::types::CellReferenceRC, number_format::to_excel_precision_str};
 
@@ -34,10 +35,21 @@ pub enum DisplaceData {
     None,
 }
 
+/// This is the internal mode in IronCalc
 pub fn to_rc_format(node: &Node) -> String {
     stringify(node, None, &DisplaceData::None, false)
 }
 
+/// This is the mode used to display the formula in the UI
+pub fn to_string(node: &Node, context: &CellReferenceRC) -> String {
+    stringify(node, Some(context), &DisplaceData::None, false)
+}
+
+/// This is the mode used to export the formula to Excel
+pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String {
+    stringify(node, Some(context), &DisplaceData::None, true)
+}
+
 pub fn to_string_displaced(
     node: &Node,
     context: &CellReferenceRC,
@@ -46,18 +58,10 @@ pub fn to_string_displaced(
     stringify(node, Some(context), displace_data, false)
 }
 
-pub fn to_string(node: &Node, context: &CellReferenceRC) -> String {
-    stringify(node, Some(context), &DisplaceData::None, false)
-}
-
-pub fn to_excel_string(node: &Node, context: &CellReferenceRC) -> String {
-    stringify(node, Some(context), &DisplaceData::None, true)
-}
-
 /// Converts a local reference to a string applying some displacement if needed.
 /// It uses A1 style if context is not None. If context is None it uses R1C1 style
 /// If full_row is true then the row details will be omitted in the A1 case
-/// If full_colum is true then column details will be omitted.
+/// If full_column is true then column details will be omitted.
 pub(crate) fn stringify_reference(
     context: Option<&CellReferenceRC>,
     displace_data: &DisplaceData,
@@ -235,7 +239,7 @@ fn format_function(
     args: &Vec,
     context: Option<&CellReferenceRC>,
     displace_data: &DisplaceData,
-    use_original_name: bool,
+    export_to_excel: bool,
 ) -> String {
     let mut first = true;
     let mut arguments = "".to_string();
@@ -244,11 +248,11 @@ fn format_function(
             arguments = format!(
                 "{},{}",
                 arguments,
-                stringify(el, context, displace_data, use_original_name)
+                stringify(el, context, displace_data, export_to_excel)
             );
         } else {
             first = false;
-            arguments = stringify(el, context, displace_data, use_original_name);
+            arguments = stringify(el, context, displace_data, export_to_excel);
         }
     }
     format!("{}({})", name, arguments)
@@ -258,7 +262,7 @@ fn stringify(
     node: &Node,
     context: Option<&CellReferenceRC>,
     displace_data: &DisplaceData,
-    use_original_name: bool,
+    export_to_excel: bool,
 ) -> String {
     use self::Node::*;
     match node {
@@ -407,52 +411,52 @@ fn stringify(
         }
         OpRangeKind { left, right } => format!(
             "{}:{}",
-            stringify(left, context, displace_data, use_original_name),
-            stringify(right, context, displace_data, use_original_name)
+            stringify(left, context, displace_data, export_to_excel),
+            stringify(right, context, displace_data, export_to_excel)
         ),
         OpConcatenateKind { left, right } => format!(
             "{}&{}",
-            stringify(left, context, displace_data, use_original_name),
-            stringify(right, context, displace_data, use_original_name)
+            stringify(left, context, displace_data, export_to_excel),
+            stringify(right, context, displace_data, export_to_excel)
         ),
         CompareKind { kind, left, right } => format!(
             "{}{}{}",
-            stringify(left, context, displace_data, use_original_name),
+            stringify(left, context, displace_data, export_to_excel),
             kind,
-            stringify(right, context, displace_data, use_original_name)
+            stringify(right, context, displace_data, export_to_excel)
         ),
         OpSumKind { kind, left, right } => format!(
             "{}{}{}",
-            stringify(left, context, displace_data, use_original_name),
+            stringify(left, context, displace_data, export_to_excel),
             kind,
-            stringify(right, context, displace_data, use_original_name)
+            stringify(right, context, displace_data, export_to_excel)
         ),
         OpProductKind { kind, left, right } => {
             let x = match **left {
                 OpSumKind { .. } => format!(
                     "({})",
-                    stringify(left, context, displace_data, use_original_name)
+                    stringify(left, context, displace_data, export_to_excel)
                 ),
                 CompareKind { .. } => format!(
                     "({})",
-                    stringify(left, context, displace_data, use_original_name)
+                    stringify(left, context, displace_data, export_to_excel)
                 ),
-                _ => stringify(left, context, displace_data, use_original_name),
+                _ => stringify(left, context, displace_data, export_to_excel),
             };
             let y = match **right {
                 OpSumKind { .. } => format!(
                     "({})",
-                    stringify(right, context, displace_data, use_original_name)
+                    stringify(right, context, displace_data, export_to_excel)
                 ),
                 CompareKind { .. } => format!(
                     "({})",
-                    stringify(right, context, displace_data, use_original_name)
+                    stringify(right, context, displace_data, export_to_excel)
                 ),
                 OpProductKind { .. } => format!(
                     "({})",
-                    stringify(right, context, displace_data, use_original_name)
+                    stringify(right, context, displace_data, export_to_excel)
                 ),
-                _ => stringify(right, context, displace_data, use_original_name),
+                _ => stringify(right, context, displace_data, export_to_excel),
             };
             format!("{}{}{}", x, kind, y)
         }
@@ -467,9 +471,7 @@ fn stringify(
                 | DefinedNameKind(_)
                 | TableNameKind(_)
                 | WrongVariableKind(_)
-                | WrongRangeKind { .. } => {
-                    stringify(left, context, displace_data, use_original_name)
-                }
+                | WrongRangeKind { .. } => stringify(left, context, displace_data, export_to_excel),
                 OpRangeKind { .. }
                 | OpConcatenateKind { .. }
                 | OpProductKind { .. }
@@ -482,9 +484,10 @@ fn stringify(
                 | ParseErrorKind { .. }
                 | OpSumKind { .. }
                 | CompareKind { .. }
+                | ImplicitIntersection { .. }
                 | EmptyArgKind => format!(
                     "({})",
-                    stringify(left, context, displace_data, use_original_name)
+                    stringify(left, context, displace_data, export_to_excel)
                 ),
             };
             let y = match **right {
@@ -498,7 +501,7 @@ fn stringify(
                 | TableNameKind(_)
                 | WrongVariableKind(_)
                 | WrongRangeKind { .. } => {
-                    stringify(right, context, displace_data, use_original_name)
+                    stringify(right, context, displace_data, export_to_excel)
                 }
                 OpRangeKind { .. }
                 | OpConcatenateKind { .. }
@@ -512,23 +515,24 @@ fn stringify(
                 | ParseErrorKind { .. }
                 | OpSumKind { .. }
                 | CompareKind { .. }
+                | ImplicitIntersection { .. }
                 | EmptyArgKind => format!(
                     "({})",
-                    stringify(right, context, displace_data, use_original_name)
+                    stringify(right, context, displace_data, export_to_excel)
                 ),
             };
             format!("{}^{}", x, y)
         }
         InvalidFunctionKind { name, args } => {
-            format_function(name, args, context, displace_data, use_original_name)
+            format_function(name, args, context, displace_data, export_to_excel)
         }
         FunctionKind { kind, args } => {
-            let name = if use_original_name {
+            let name = if export_to_excel {
                 kind.to_xlsx_string()
             } else {
                 kind.to_string()
             };
-            format_function(&name, args, context, displace_data, use_original_name)
+            format_function(&name, args, context, displace_data, export_to_excel)
         }
         ArrayKind(args) => {
             let mut first = true;
@@ -538,29 +542,29 @@ fn stringify(
                     arguments = format!(
                         "{},{}",
                         arguments,
-                        stringify(el, context, displace_data, use_original_name)
+                        stringify(el, context, displace_data, export_to_excel)
                     );
                 } else {
                     first = false;
-                    arguments = stringify(el, context, displace_data, use_original_name);
+                    arguments = stringify(el, context, displace_data, export_to_excel);
                 }
             }
             format!("{{{}}}", arguments)
         }
         TableNameKind(value) => value.to_string(),
-        DefinedNameKind((name, _)) => name.to_string(),
+        DefinedNameKind((name, ..)) => name.to_string(),
         WrongVariableKind(name) => name.to_string(),
         UnaryKind { kind, right } => match kind {
             OpUnary::Minus => {
                 format!(
                     "-{}",
-                    stringify(right, context, displace_data, use_original_name)
+                    stringify(right, context, displace_data, export_to_excel)
                 )
             }
             OpUnary::Percentage => {
                 format!(
                     "{}%",
-                    stringify(right, context, displace_data, use_original_name)
+                    stringify(right, context, displace_data, export_to_excel)
                 )
             }
         },
@@ -571,6 +575,29 @@ fn stringify(
             message: _,
         } => formula.to_string(),
         EmptyArgKind => "".to_string(),
+        ImplicitIntersection {
+            automatic: _,
+            child,
+        } => {
+            if export_to_excel {
+                // We need to check wether the II can be automatic or not
+                let mut new_node = child.as_ref().clone();
+
+                add_implicit_intersection(&mut new_node, true);
+                if matches!(&new_node, Node::ImplicitIntersection { .. }) {
+                    return stringify(child, context, displace_data, export_to_excel);
+                }
+
+                return format!(
+                    "_xlfn.SINGLE({})",
+                    stringify(child, context, displace_data, export_to_excel)
+                );
+            }
+            format!(
+                "@{}",
+                stringify(child, context, displace_data, export_to_excel)
+            )
+        }
     }
 }
 
@@ -658,6 +685,12 @@ pub(crate) fn rename_sheet_in_node(node: &mut Node, sheet_index: u32, new_name:
         Node::UnaryKind { kind: _, right } => {
             rename_sheet_in_node(right, sheet_index, new_name);
         }
+        Node::ImplicitIntersection {
+            automatic: _,
+            child,
+        } => {
+            rename_sheet_in_node(child, sheet_index, new_name);
+        }
 
         // Do nothing
         Node::BooleanKind(_) => {}
@@ -681,7 +714,7 @@ pub(crate) fn rename_defined_name_in_node(
 ) {
     match node {
         // Rename
-        Node::DefinedNameKind((n, s)) => {
+        Node::DefinedNameKind((n, s, _)) => {
             if name.to_lowercase() == n.to_lowercase() && *s == scope {
                 *n = new_name.to_string();
             }
@@ -736,6 +769,12 @@ pub(crate) fn rename_defined_name_in_node(
         Node::UnaryKind { kind: _, right } => {
             rename_defined_name_in_node(right, name, scope, new_name);
         }
+        Node::ImplicitIntersection {
+            automatic: _,
+            child,
+        } => {
+            rename_defined_name_in_node(child, name, scope, new_name);
+        }
 
         // Do nothing
         Node::BooleanKind(_) => {}
diff --git a/base/src/expressions/parser/tests/mod.rs b/base/src/expressions/parser/tests/mod.rs
index 7661338..009514a 100644
--- a/base/src/expressions/parser/tests/mod.rs
+++ b/base/src/expressions/parser/tests/mod.rs
@@ -1,4 +1,6 @@
+mod test_add_implicit_intersection;
 mod test_general;
+mod test_implicit_intersection;
 mod test_issue_155;
 mod test_move_formula;
 mod test_ranges;
diff --git a/base/src/expressions/parser/tests/test_add_implicit_intersection.rs b/base/src/expressions/parser/tests/test_add_implicit_intersection.rs
new file mode 100644
index 0000000..53abbe5
--- /dev/null
+++ b/base/src/expressions/parser/tests/test_add_implicit_intersection.rs
@@ -0,0 +1,80 @@
+use std::collections::HashMap;
+
+use crate::expressions::{
+    parser::{
+        stringify::{to_excel_string, to_string},
+        Parser,
+    },
+    types::CellReferenceRC,
+};
+
+use crate::expressions::parser::static_analysis::add_implicit_intersection;
+
+#[test]
+fn simple_test() {
+    let worksheets = vec!["Sheet1".to_string()];
+    let mut parser = Parser::new(worksheets, vec![], HashMap::new());
+
+    // Reference cell is Sheet1!A1
+    let cell_reference = CellReferenceRC {
+        sheet: "Sheet1".to_string(),
+        row: 1,
+        column: 1,
+    };
+    let cases = vec![
+        ("A1:A10*SUM(A1:A10)", "@A1:A10*SUM(A1:A10)"),
+        ("A1:A10", "@A1:A10"),
+        // Math and trigonometry functions
+        ("SUM(A1:A10)", "SUM(A1:A10)"),
+        ("SIN(A1:A10)", "SIN(@A1:A10)"),
+        ("COS(A1:A10)", "COS(@A1:A10)"),
+        ("TAN(A1:A10)", "TAN(@A1:A10)"),
+        ("ASIN(A1:A10)", "ASIN(@A1:A10)"),
+        ("ACOS(A1:A10)", "ACOS(@A1:A10)"),
+        ("ATAN(A1:A10)", "ATAN(@A1:A10)"),
+        ("SINH(A1:A10)", "SINH(@A1:A10)"),
+        ("COSH(A1:A10)", "COSH(@A1:A10)"),
+        ("TANH(A1:A10)", "TANH(@A1:A10)"),
+        ("ASINH(A1:A10)", "ASINH(@A1:A10)"),
+        ("ACOSH(A1:A10)", "ACOSH(@A1:A10)"),
+        ("ATANH(A1:A10)", "ATANH(@A1:A10)"),
+        ("ATAN2(A1:A10,B1:B10)", "ATAN2(@A1:A10,@B1:B10)"),
+        ("ATAN2(A1:A10,A1)", "ATAN2(@A1:A10,A1)"),
+        ("SQRT(A1:A10)", "SQRT(@A1:A10)"),
+        ("SQRTPI(A1:A10)", "SQRTPI(@A1:A10)"),
+        ("POWER(A1:A10,A1)", "POWER(@A1:A10,A1)"),
+        ("POWER(A1:A10,B1:B10)", "POWER(@A1:A10,@B1:B10)"),
+        ("MAX(A1:A10)", "MAX(A1:A10)"),
+        ("MIN(A1:A10)", "MIN(A1:A10)"),
+        ("ABS(A1:A10)", "ABS(@A1:A10)"),
+        ("FALSE()", "FALSE()"),
+        ("TRUE()", "TRUE()"),
+        // Defined names
+        ("BADNMAE", "@BADNMAE"),
+        // Logical
+        ("AND(A1:A10)", "AND(A1:A10)"),
+        ("OR(A1:A10)", "OR(A1:A10)"),
+        ("NOT(A1:A10)", "NOT(@A1:A10)"),
+        ("IF(A1:A10,B1:B10,C1:C10)", "IF(@A1:A10,@B1:B10,@C1:C10)"),
+        // Information
+        // ("ISBLANK(A1:A10)", "ISBLANK(A1:A10)"),
+        // ("ISERR(A1:A10)", "ISERR(A1:A10)"),
+        // ("ISERROR(A1:A10)", "ISERROR(A1:A10)"),
+        // ("ISEVEN(A1:A10)", "ISEVEN(A1:A10)"),
+        // ("ISLOGICAL(A1:A10)", "ISLOGICAL(A1:A10)"),
+        // ("ISNA(A1:A10)", "ISNA(A1:A10)"),
+        // ("ISNONTEXT(A1:A10)", "ISNONTEXT(A1:A10)"),
+        // ("ISNUMBER(A1:A10)", "ISNUMBER(A1:A10)"),
+        // ("ISODD(A1:A10)", "ISODD(A1:A10)"),
+        // ("ISREF(A1:A10)", "ISREF(A1:A10)"),
+        // ("ISTEXT(A1:A10)", "ISTEXT(A1:A10)"),
+    ];
+    for (formula, expected) in cases {
+        let mut t = parser.parse(formula, &cell_reference);
+        add_implicit_intersection(&mut t, true);
+        let r = to_string(&t, &cell_reference);
+        assert_eq!(r, expected);
+        let excel_formula = to_excel_string(&t, &cell_reference);
+        assert_eq!(excel_formula, formula);
+    }
+}
diff --git a/base/src/expressions/parser/tests/test_implicit_intersection.rs b/base/src/expressions/parser/tests/test_implicit_intersection.rs
new file mode 100644
index 0000000..ab555bb
--- /dev/null
+++ b/base/src/expressions/parser/tests/test_implicit_intersection.rs
@@ -0,0 +1,75 @@
+#![allow(clippy::panic)]
+
+use crate::expressions::parser::{Node, Parser};
+use crate::expressions::types::CellReferenceRC;
+use std::collections::HashMap;
+
+#[test]
+fn simple() {
+    let worksheets = vec!["Sheet1".to_string()];
+    let mut parser = Parser::new(worksheets, vec![], HashMap::new());
+
+    // Reference cell is Sheet1!B3
+    let cell_reference = CellReferenceRC {
+        sheet: "Sheet1".to_string(),
+        row: 3,
+        column: 2,
+    };
+    let t = parser.parse("@A1:A10", &cell_reference);
+    let child = Node::RangeKind {
+        sheet_name: None,
+        sheet_index: 0,
+        absolute_row1: false,
+        absolute_column1: false,
+        row1: -2,
+        column1: -1,
+        absolute_row2: false,
+        absolute_column2: false,
+        row2: 7,
+        column2: -1,
+    };
+    assert_eq!(
+        t,
+        Node::ImplicitIntersection {
+            automatic: false,
+            child: Box::new(child)
+        }
+    )
+}
+
+#[test]
+fn simple_add() {
+    let worksheets = vec!["Sheet1".to_string()];
+    let mut parser = Parser::new(worksheets, vec![], HashMap::new());
+
+    // Reference cell is Sheet1!B3
+    let cell_reference = CellReferenceRC {
+        sheet: "Sheet1".to_string(),
+        row: 3,
+        column: 2,
+    };
+    let t = parser.parse("@A1:A10+12", &cell_reference);
+    let child = Node::RangeKind {
+        sheet_name: None,
+        sheet_index: 0,
+        absolute_row1: false,
+        absolute_column1: false,
+        row1: -2,
+        column1: -1,
+        absolute_row2: false,
+        absolute_column2: false,
+        row2: 7,
+        column2: -1,
+    };
+    assert_eq!(
+        t,
+        Node::OpSumKind {
+            kind: crate::expressions::token::OpSum::Add,
+            left: Box::new(Node::ImplicitIntersection {
+                automatic: false,
+                child: Box::new(child)
+            }),
+            right: Box::new(Node::NumberKind(12.0))
+        }
+    )
+}
diff --git a/base/src/expressions/parser/tests/test_move_formula.rs b/base/src/expressions/parser/tests/test_move_formula.rs
index 37fde78..4e36c72 100644
--- a/base/src/expressions/parser/tests/test_move_formula.rs
+++ b/base/src/expressions/parser/tests/test_move_formula.rs
@@ -387,7 +387,7 @@ fn test_move_formula_misc() {
         width: 4,
         height: 5,
     };
-    let node = parser.parse("X9^C2-F4*H2", context);
+    let node = parser.parse("X9^C2-F4*H2+SUM(F2:H4)+SUM(C2:F6)", context);
     let t = move_formula(
         &node,
         &MoveContext {
@@ -400,7 +400,7 @@ fn test_move_formula_misc() {
             column_delta: 10,
         },
     );
-    assert_eq!(t, "X9^M12-P14*H2");
+    assert_eq!(t, "X9^M12-P14*H2+SUM(F2:H4)+SUM(M12:P16)");
 
     let node = parser.parse("F5*(-D5)*SUM(A1, X9, $D$5)", context);
     let t = move_formula(
@@ -475,3 +475,77 @@ fn test_move_formula_another_sheet() {
         "Sheet1!AB31*SUM(Sheet1!JJ3:JJ4)+SUM(Sheet2!C2:F6)*SUM(M12:P16)"
     );
 }
+
+#[test]
+fn move_formula_implicit_intersetion() {
+    // context is E4
+    let row = 4;
+    let column = 5;
+    let context = &CellReferenceRC {
+        sheet: "Sheet1".to_string(),
+        row,
+        column,
+    };
+    let worksheets = vec!["Sheet1".to_string()];
+    let mut parser = Parser::new(worksheets, vec![], HashMap::new());
+
+    // Area is C2:F6
+    let area = &Area {
+        sheet: 0,
+        row: 2,
+        column: 3,
+        width: 4,
+        height: 5,
+    };
+    let node = parser.parse("SUM(@F2:H4)+SUM(@C2:F6)", context);
+    let t = move_formula(
+        &node,
+        &MoveContext {
+            source_sheet_name: "Sheet1",
+            row,
+            column,
+            area,
+            target_sheet_name: "Sheet1",
+            row_delta: 10,
+            column_delta: 10,
+        },
+    );
+    assert_eq!(t, "SUM(@F2:H4)+SUM(@M12:P16)");
+}
+
+#[test]
+fn move_formula_implicit_intersetion_with_ranges() {
+    // context is E4
+    let row = 4;
+    let column = 5;
+    let context = &CellReferenceRC {
+        sheet: "Sheet1".to_string(),
+        row,
+        column,
+    };
+    let worksheets = vec!["Sheet1".to_string()];
+    let mut parser = Parser::new(worksheets, vec![], HashMap::new());
+
+    // Area is C2:F6
+    let area = &Area {
+        sheet: 0,
+        row: 2,
+        column: 3,
+        width: 4,
+        height: 5,
+    };
+    let node = parser.parse("SUM(@F2:H4)+SUM(@C2:F6)+SUM(@A1, @X9, @$D$5)", context);
+    let t = move_formula(
+        &node,
+        &MoveContext {
+            source_sheet_name: "Sheet1",
+            row,
+            column,
+            area,
+            target_sheet_name: "Sheet1",
+            row_delta: 10,
+            column_delta: 10,
+        },
+    );
+    assert_eq!(t, "SUM(@F2:H4)+SUM(@M12:P16)+SUM(@A1,@X9,@$N$15)");
+}
diff --git a/base/src/expressions/parser/tests/test_tables.rs b/base/src/expressions/parser/tests/test_tables.rs
index 3addf98..eced95d 100644
--- a/base/src/expressions/parser/tests/test_tables.rs
+++ b/base/src/expressions/parser/tests/test_tables.rs
@@ -3,11 +3,10 @@
 use std::collections::HashMap;
 
 use crate::expressions::parser::stringify::to_string;
-use crate::expressions::utils::{number_to_column, parse_reference_a1};
-use crate::types::{Table, TableColumn, TableStyleInfo};
-
 use crate::expressions::parser::Parser;
 use crate::expressions::types::CellReferenceRC;
+use crate::expressions::utils::{number_to_column, parse_reference_a1};
+use crate::types::{Table, TableColumn, TableStyleInfo};
 
 fn create_test_table(
     table_name: &str,
diff --git a/base/src/expressions/parser/walk.rs b/base/src/expressions/parser/walk.rs
deleted file mode 100644
index 746e379..0000000
--- a/base/src/expressions/parser/walk.rs
+++ /dev/null
@@ -1,278 +0,0 @@
-use super::{move_formula::ref_is_in_area, Node};
-
-use crate::expressions::types::{Area, CellReferenceIndex};
-
-pub(crate) fn forward_references(
-    node: &mut Node,
-    context: &CellReferenceIndex,
-    source_area: &Area,
-    target_sheet: u32,
-    target_sheet_name: &str,
-    target_row: i32,
-    target_column: i32,
-) {
-    match node {
-        Node::ReferenceKind {
-            sheet_name,
-            sheet_index: reference_sheet,
-            absolute_row,
-            absolute_column,
-            row: reference_row,
-            column: reference_column,
-        } => {
-            let reference_row_absolute = if *absolute_row {
-                *reference_row
-            } else {
-                *reference_row + context.row
-            };
-            let reference_column_absolute = if *absolute_column {
-                *reference_column
-            } else {
-                *reference_column + context.column
-            };
-            if ref_is_in_area(
-                *reference_sheet,
-                reference_row_absolute,
-                reference_column_absolute,
-                source_area,
-            ) {
-                if *reference_sheet != target_sheet {
-                    *sheet_name = Some(target_sheet_name.to_string());
-                    *reference_sheet = target_sheet;
-                }
-                *reference_row = target_row + *reference_row - source_area.row;
-                *reference_column = target_column + *reference_column - source_area.column;
-            }
-        }
-        Node::RangeKind {
-            sheet_name,
-            sheet_index,
-            absolute_row1,
-            absolute_column1,
-            row1,
-            column1,
-            absolute_row2,
-            absolute_column2,
-            row2,
-            column2,
-        } => {
-            let reference_row1 = if *absolute_row1 {
-                *row1
-            } else {
-                *row1 + context.row
-            };
-            let reference_column1 = if *absolute_column1 {
-                *column1
-            } else {
-                *column1 + context.column
-            };
-
-            let reference_row2 = if *absolute_row2 {
-                *row2
-            } else {
-                *row2 + context.row
-            };
-            let reference_column2 = if *absolute_column2 {
-                *column2
-            } else {
-                *column2 + context.column
-            };
-            if ref_is_in_area(*sheet_index, reference_row1, reference_column1, source_area)
-                && ref_is_in_area(*sheet_index, reference_row2, reference_column2, source_area)
-            {
-                if *sheet_index != target_sheet {
-                    *sheet_index = target_sheet;
-                    *sheet_name = Some(target_sheet_name.to_string());
-                }
-                *row1 = target_row + *row1 - source_area.row;
-                *column1 = target_column + *column1 - source_area.column;
-                *row2 = target_row + *row2 - source_area.row;
-                *column2 = target_column + *column2 - source_area.column;
-            }
-        }
-        // Recurse
-        Node::OpRangeKind { left, right } => {
-            forward_references(
-                left,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-            forward_references(
-                right,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-        }
-        Node::OpConcatenateKind { left, right } => {
-            forward_references(
-                left,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-            forward_references(
-                right,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-        }
-        Node::OpSumKind {
-            kind: _,
-            left,
-            right,
-        } => {
-            forward_references(
-                left,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-            forward_references(
-                right,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-        }
-        Node::OpProductKind {
-            kind: _,
-            left,
-            right,
-        } => {
-            forward_references(
-                left,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-            forward_references(
-                right,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-        }
-        Node::OpPowerKind { left, right } => {
-            forward_references(
-                left,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-            forward_references(
-                right,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-        }
-        Node::FunctionKind { kind: _, args } => {
-            for arg in args {
-                forward_references(
-                    arg,
-                    context,
-                    source_area,
-                    target_sheet,
-                    target_sheet_name,
-                    target_row,
-                    target_column,
-                );
-            }
-        }
-        Node::InvalidFunctionKind { name: _, args } => {
-            for arg in args {
-                forward_references(
-                    arg,
-                    context,
-                    source_area,
-                    target_sheet,
-                    target_sheet_name,
-                    target_row,
-                    target_column,
-                );
-            }
-        }
-        Node::CompareKind {
-            kind: _,
-            left,
-            right,
-        } => {
-            forward_references(
-                left,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-            forward_references(
-                right,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-        }
-        Node::UnaryKind { kind: _, right } => {
-            forward_references(
-                right,
-                context,
-                source_area,
-                target_sheet,
-                target_sheet_name,
-                target_row,
-                target_column,
-            );
-        }
-        // TODO: Not implemented
-        Node::ArrayKind(_) => {}
-        // Do nothing. Note: we could do a blanket _ => {}
-        Node::DefinedNameKind(_) => {}
-        Node::TableNameKind(_) => {}
-        Node::WrongVariableKind(_) => {}
-        Node::ErrorKind(_) => {}
-        Node::ParseErrorKind { .. } => {}
-        Node::EmptyArgKind => {}
-        Node::BooleanKind(_) => {}
-        Node::NumberKind(_) => {}
-        Node::StringKind(_) => {}
-        Node::WrongReferenceKind { .. } => {}
-        Node::WrongRangeKind { .. } => {}
-    }
-}
diff --git a/base/src/expressions/token.rs b/base/src/expressions/token.rs
index 877ddc6..cd98144 100644
--- a/base/src/expressions/token.rs
+++ b/base/src/expressions/token.rs
@@ -240,6 +240,7 @@ pub enum TokenType {
     Bang,               // !
     Percent,            // %
     And,                // &
+    At,                 // @
     Reference {
         sheet: Option,
         row: i32,
diff --git a/base/src/functions/information.rs b/base/src/functions/information.rs
index 44d847c..5901539 100644
--- a/base/src/functions/information.rs
+++ b/base/src/functions/information.rs
@@ -249,7 +249,7 @@ impl Model {
         // The arg could be a defined name or a table
         // let  = &args[0];
         match &args[0] {
-            Node::DefinedNameKind((name, scope)) => {
+            Node::DefinedNameKind((name, scope, _)) => {
                 // Let's see if it is a defined name
                 if let Some(defined_name) = self
                     .parsed_defined_names
diff --git a/base/src/functions/lookup_and_reference.rs b/base/src/functions/lookup_and_reference.rs
index 04a9e95..f8f9735 100644
--- a/base/src/functions/lookup_and_reference.rs
+++ b/base/src/functions/lookup_and_reference.rs
@@ -855,7 +855,7 @@ impl Model {
             if left.row != right.row || left.column != right.column {
                 // FIXME: Implicit intersection or dynamic arrays
                 return CalcResult::Error {
-                    error: Error::ERROR,
+                    error: Error::NIMPL,
                     origin: cell,
                     message: "argument must be a reference to a single cell".to_string(),
                 };
diff --git a/base/src/lib.rs b/base/src/lib.rs
index 840a969..ec25fc9 100644
--- a/base/src/lib.rs
+++ b/base/src/lib.rs
@@ -41,7 +41,6 @@ pub mod worksheet;
 mod actions;
 mod cast;
 mod constants;
-mod diffs;
 mod functions;
 mod implicit_intersection;
 mod model;
diff --git a/base/src/model.rs b/base/src/model.rs
index f3ea71c..8364730 100644
--- a/base/src/model.rs
+++ b/base/src/model.rs
@@ -207,6 +207,17 @@ impl Model {
                     },
                 }
             }
+            Node::ImplicitIntersection {
+                automatic: _,
+                child,
+            } => match self.evaluate_node_with_reference(child, cell) {
+                CalcResult::Range { left, right } => CalcResult::Range { left, right },
+                _ => CalcResult::new_error(
+                    Error::ERROR,
+                    cell,
+                    format!("Error with Implicit Intersection in cell {:?}", cell),
+                ),
+            },
             _ => self.evaluate_node_in_context(node, cell),
         }
     }
@@ -416,7 +427,7 @@ impl Model {
                 // TODO: NOT IMPLEMENTED
                 CalcResult::new_error(Error::NIMPL, cell, "Arrays not implemented".to_string())
             }
-            DefinedNameKind((name, scope)) => {
+            DefinedNameKind((name, scope, _)) => {
                 if let Ok(Some(parsed_defined_name)) = self.get_parsed_defined_name(name, *scope) {
                     match parsed_defined_name {
                         ParsedDefinedName::CellReference(reference) => {
@@ -528,6 +539,22 @@ impl Model {
                 format!("Error parsing {}: {}", formula, message),
             ),
             EmptyArgKind => CalcResult::EmptyArg,
+            ImplicitIntersection {
+                automatic: _,
+                child,
+            } => match self.evaluate_node_with_reference(child, cell) {
+                CalcResult::Range { left, right } => {
+                    match implicit_intersection(&cell, &Range { left, right }) {
+                        Some(cell_reference) => self.evaluate_cell(cell_reference),
+                        None => CalcResult::new_error(
+                            Error::VALUE,
+                            cell,
+                            format!("Error with Implicit Intersection in cell {:?}", cell),
+                        ),
+                    }
+                }
+                _ => self.evaluate_node_in_context(child, cell),
+            },
         }
     }
 
@@ -617,12 +644,15 @@ impl Model {
                     };
                 }
                 CalcResult::Range { left, right } => {
-                    let range = Range {
-                        left: *left,
-                        right: *right,
-                    };
-                    if let Some(intersection_cell) = implicit_intersection(&cell_reference, &range)
+                    if left.sheet == right.sheet
+                        && left.row == right.row
+                        && left.column == right.column
                     {
+                        let intersection_cell = CellReferenceIndex {
+                            sheet: left.sheet,
+                            column: left.column,
+                            row: left.row,
+                        };
                         let v = self.evaluate_cell(intersection_cell);
                         self.set_cell_value(cell_reference, &v);
                     } else {
@@ -639,10 +669,32 @@ impl Model {
                             f,
                             s,
                             o,
-                            m: "Invalid reference".to_string(),
-                            ei: Error::VALUE,
+                            m: "Implicit Intersection not implemented".to_string(),
+                            ei: Error::NIMPL,
                         };
                     }
+                    // if let Some(intersection_cell) = implicit_intersection(&cell_reference, &range)
+                    // {
+                    //     let v = self.evaluate_cell(intersection_cell);
+                    //     self.set_cell_value(cell_reference, &v);
+                    // } else {
+                    //     let o = match self.cell_reference_to_string(&cell_reference) {
+                    //         Ok(s) => s,
+                    //         Err(_) => "".to_string(),
+                    //     };
+                    //     *self.workbook.worksheets[sheet as usize]
+                    //         .sheet_data
+                    //         .get_mut(&row)
+                    //         .expect("expected a row")
+                    //         .get_mut(&column)
+                    //         .expect("expected a column") = Cell::CellFormulaError {
+                    //         f,
+                    //         s,
+                    //         o,
+                    //         m: "Invalid reference".to_string(),
+                    //         ei: Error::VALUE,
+                    //     };
+                    // }
                 }
                 CalcResult::EmptyCell | CalcResult::EmptyArg => {
                     *self.workbook.worksheets[sheet as usize]
@@ -865,11 +917,7 @@ impl Model {
 
         let worksheet_names = worksheets.iter().map(|s| s.get_name()).collect();
 
-        let defined_names = workbook
-            .get_defined_names_with_scope()
-            .iter()
-            .map(|s| (s.0.to_owned(), s.1))
-            .collect();
+        let defined_names = workbook.get_defined_names_with_scope();
         // add all tables
         // let mut tables = Vec::new();
         // for worksheet in worksheets {
diff --git a/base/src/new_empty.rs b/base/src/new_empty.rs
index a313fe0..2e76696 100644
--- a/base/src/new_empty.rs
+++ b/base/src/new_empty.rs
@@ -144,12 +144,7 @@ impl Model {
 
     /// Reparses all formulas and defined names
     pub(crate) fn reset_parsed_structures(&mut self) {
-        let defined_names = self
-            .workbook
-            .get_defined_names_with_scope()
-            .iter()
-            .map(|s| (s.0.to_owned(), s.1))
-            .collect();
+        let defined_names = self.workbook.get_defined_names_with_scope();
         self.parser
             .set_worksheets_and_names(self.workbook.get_worksheet_names(), defined_names);
         self.parsed_formulas = vec![];
diff --git a/base/src/test/mod.rs b/base/src/test/mod.rs
index d5e8186..bfe23d2 100644
--- a/base/src/test/mod.rs
+++ b/base/src/test/mod.rs
@@ -28,7 +28,6 @@ mod test_fn_sumifs;
 mod test_fn_textbefore;
 mod test_fn_textjoin;
 mod test_fn_unicode;
-mod test_forward_references;
 mod test_frozen_rows_columns;
 mod test_general;
 mod test_math;
@@ -59,6 +58,7 @@ mod test_fn_type;
 mod test_frozen_rows_and_columns;
 mod test_geomean;
 mod test_get_cell_content;
+mod test_implicit_intersection;
 mod test_issue_155;
 mod test_percentage;
 mod test_set_functions_error_handling;
diff --git a/base/src/test/test_fn_concatenate.rs b/base/src/test/test_fn_concatenate.rs
index 727fe95..dcb2c0a 100644
--- a/base/src/test/test_fn_concatenate.rs
+++ b/base/src/test/test_fn_concatenate.rs
@@ -22,13 +22,14 @@ fn fn_concatenate() {
     model._set("B1", r#"=CONCATENATE(A1, A2, A3, "!")"#);
     // This will break once we implement the implicit intersection operator
     // It should be:
-    // model._set("B2", r#"=CONCATENATE(@A1:A3, "!")"#);
+    model._set("C2", r#"=CONCATENATE(@A1:A3, "!")"#);
     model._set("B2", r#"=CONCATENATE(A1:A3, "!")"#);
     model._set("B3", r#"=CONCAT(A1:A3, "!")"#);
 
     model.evaluate();
 
     assert_eq!(model._get_text("B1"), *"Hello my World!");
-    assert_eq!(model._get_text("B2"), *" my !");
+    assert_eq!(model._get_text("B2"), *"#N/IMPL!");
     assert_eq!(model._get_text("B3"), *"Hello my World!");
+    assert_eq!(model._get_text("C2"), *" my !");
 }
diff --git a/base/src/test/test_fn_formulatext.rs b/base/src/test/test_fn_formulatext.rs
index bfe338d..b15180e 100644
--- a/base/src/test/test_fn_formulatext.rs
+++ b/base/src/test/test_fn_formulatext.rs
@@ -30,8 +30,18 @@ fn implicit_intersection() {
     model._set("A2", "=FORMULATEXT(D1:E1)");
     model.evaluate();
 
-    assert_eq!(model._get_text("A1"), *"#ERROR!");
-    assert_eq!(model._get_text("A2"), *"#ERROR!");
+    assert_eq!(model._get_text("A1"), *"#N/IMPL!");
+    assert_eq!(model._get_text("A2"), *"#N/IMPL!");
+}
+
+#[test]
+fn implicit_intersection_operator() {
+    let mut model = new_empty_model();
+    model._set("A1", "=1 +  2");
+    model._set("B1", "=FORMULATEXT(@A:A)");
+    model.evaluate();
+
+    assert_eq!(model._get_text("B1"), *"#N/IMPL!");
 }
 
 #[test]
diff --git a/base/src/test/test_forward_references.rs b/base/src/test/test_forward_references.rs
deleted file mode 100644
index 0b8aa23..0000000
--- a/base/src/test/test_forward_references.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-#![allow(clippy::unwrap_used)]
-
-use crate::expressions::types::{Area, CellReferenceIndex};
-use crate::test::util::new_empty_model;
-
-#[test]
-fn test_forward_references() {
-    let mut model = new_empty_model();
-
-    // test single ref changed nd not changed
-    model._set("H8", "=F6*G9");
-    // tests areas
-    model._set("H9", "=SUM(D4:F6)");
-    // absolute coordinates
-    model._set("H10", "=$F$6");
-    // area larger than the source area
-    model._set("H11", "=SUM(D3:F6)");
-    // Test arguments and concat
-    model._set("H12", "=SUM(F6, D4:F6) & D4");
-    // Test range operator. This is syntax error for now.
-    // model._set("H13", "=SUM(D4:INDEX(D4:F5,4,2))");
-    // Test operations
-    model._set("H14", "=-D4+D5*F6/F5");
-
-    model.evaluate();
-
-    // Source Area is D4:F6
-    let source_area = &Area {
-        sheet: 0,
-        row: 4,
-        column: 4,
-        width: 3,
-        height: 3,
-    };
-
-    // We paste in B10
-    let target_row = 10;
-    let target_column = 2;
-    let result = model.forward_references(
-        source_area,
-        &CellReferenceIndex {
-            sheet: 0,
-            row: target_row,
-            column: target_column,
-        },
-    );
-    assert!(result.is_ok());
-    model.evaluate();
-
-    assert_eq!(model._get_formula("H8"), "=D12*G9");
-    assert_eq!(model._get_formula("H9"), "=SUM(B10:D12)");
-    assert_eq!(model._get_formula("H10"), "=$D$12");
-
-    assert_eq!(model._get_formula("H11"), "=SUM(D3:F6)");
-    assert_eq!(model._get_formula("H12"), "=SUM(D12,B10:D12)&B10");
-    // assert_eq!(model._get_formula("H13"), "=SUM(B10:INDEX(B10:D11,4,2))");
-    assert_eq!(model._get_formula("H14"), "=-B10+B11*D12/D11");
-}
-
-#[test]
-fn test_different_sheet() {
-    let mut model = new_empty_model();
-
-    // test single ref changed not changed
-    model._set("H8", "=F6*G9");
-    // tests areas
-    model._set("H9", "=SUM(D4:F6)");
-    // absolute coordinates
-    model._set("H10", "=$F$6");
-    // area larger than the source area
-    model._set("H11", "=SUM(D3:F6)");
-    // Test arguments and concat
-    model._set("H12", "=SUM(F6, D4:F6) & D4");
-    // Test range operator. This is syntax error for now.
-    // model._set("H13", "=SUM(D4:INDEX(D4:F5,4,2))");
-    // Test operations
-    model._set("H14", "=-D4+D5*F6/F5");
-
-    // Adds a new sheet
-    assert!(model.add_sheet("Sheet2").is_ok());
-
-    model.evaluate();
-
-    // Source Area is D4:F6
-    let source_area = &Area {
-        sheet: 0,
-        row: 4,
-        column: 4,
-        width: 3,
-        height: 3,
-    };
-
-    // We paste in Sheet2!B10
-    let target_row = 10;
-    let target_column = 2;
-    let result = model.forward_references(
-        source_area,
-        &CellReferenceIndex {
-            sheet: 1,
-            row: target_row,
-            column: target_column,
-        },
-    );
-    assert!(result.is_ok());
-    model.evaluate();
-
-    assert_eq!(model._get_formula("H8"), "=Sheet2!D12*G9");
-    assert_eq!(model._get_formula("H9"), "=SUM(Sheet2!B10:D12)");
-    assert_eq!(model._get_formula("H10"), "=Sheet2!$D$12");
-
-    assert_eq!(model._get_formula("H11"), "=SUM(D3:F6)");
-    assert_eq!(
-        model._get_formula("H12"),
-        "=SUM(Sheet2!D12,Sheet2!B10:D12)&Sheet2!B10"
-    );
-    // assert_eq!(model._get_formula("H13"), "=SUM(B10:INDEX(B10:D11,4,2))");
-    assert_eq!(
-        model._get_formula("H14"),
-        "=-Sheet2!B10+Sheet2!B11*Sheet2!D12/Sheet2!D11"
-    );
-}
diff --git a/base/src/test/test_implicit_intersection.rs b/base/src/test/test_implicit_intersection.rs
new file mode 100644
index 0000000..43a039b
--- /dev/null
+++ b/base/src/test/test_implicit_intersection.rs
@@ -0,0 +1,50 @@
+#![allow(clippy::unwrap_used)]
+
+use crate::test::util::new_empty_model;
+
+#[test]
+fn simple_colum() {
+    let mut model = new_empty_model();
+    // We populate cells A1 to A3
+    model._set("A1", "1");
+    model._set("A2", "2");
+    model._set("A3", "3");
+
+    model._set("C2", "=@A1:A3");
+
+    model.evaluate();
+
+    assert_eq!(model._get_text("C2"), "2".to_string());
+}
+
+#[test]
+fn return_of_array_is_n_impl() {
+    let mut model = new_empty_model();
+    // We populate cells A1 to A3
+    model._set("A1", "1");
+    model._set("A2", "2");
+    model._set("A3", "3");
+
+    model._set("C2", "=A1:A3");
+    model._set("D2", "=SUM(SIN(A:A)");
+
+    model.evaluate();
+
+    assert_eq!(model._get_text("C2"), "#N/IMPL!".to_string());
+    assert_eq!(model._get_text("D2"), "#N/IMPL!".to_string());
+}
+
+#[test]
+fn concat() {
+    let mut model = new_empty_model();
+    model._set("A1", "=CONCAT(@B1:B3)");
+    model._set("A2", "=CONCAT(B1:B3)");
+    model._set("B1", "Hello");
+    model._set("B2", " ");
+    model._set("B3", "world!");
+
+    model.evaluate();
+
+    assert_eq!(model._get_text("A1"), *"Hello");
+    assert_eq!(model._get_text("A2"), *"Hello world!");
+}
diff --git a/base/src/units.rs b/base/src/units.rs
index 1b71865..533f0e7 100644
--- a/base/src/units.rs
+++ b/base/src/units.rs
@@ -299,6 +299,7 @@ impl Model {
             Node::WrongVariableKind(_) => None,
             Node::CompareKind { .. } => None,
             Node::OpPowerKind { .. } => None,
+            Node::ImplicitIntersection { .. } => None,
         }
     }
 
diff --git a/base/src/workbook.rs b/base/src/workbook.rs
index 841537d..53ad0ff 100644
--- a/base/src/workbook.rs
+++ b/base/src/workbook.rs
@@ -1,6 +1,6 @@
 use std::vec::Vec;
 
-use crate::types::*;
+use crate::{expressions::parser::DefinedNameS, types::*};
 
 impl Workbook {
     pub fn get_worksheet_names(&self) -> Vec {
@@ -29,7 +29,7 @@ impl Workbook {
     }
 
     /// Returns the a list of defined names in the workbook with their scope
-    pub fn get_defined_names_with_scope(&self) -> Vec<(String, Option, String)> {
+    pub fn get_defined_names_with_scope(&self) -> Vec {
         let sheet_id_index: Vec = self.worksheets.iter().map(|s| s.sheet_id).collect();
 
         let defined_names = self
diff --git a/xlsx/src/import/worksheets.rs b/xlsx/src/import/worksheets.rs
index 1c450b5..a446660 100644
--- a/xlsx/src/import/worksheets.rs
+++ b/xlsx/src/import/worksheets.rs
@@ -1,10 +1,11 @@
 #![allow(clippy::unwrap_used)]
 
+use ironcalc_base::expressions::parser::static_analysis::add_implicit_intersection;
 use std::{collections::HashMap, io::Read, num::ParseIntError};
 
 use ironcalc_base::{
     expressions::{
-        parser::{stringify::to_rc_format, Parser},
+        parser::{stringify::to_rc_format, DefinedNameS, Parser},
         token::{get_error_by_english_name, Error},
         types::CellReferenceRC,
         utils::{column_to_number, parse_reference_a1},
@@ -42,7 +43,7 @@ pub(crate) struct Relationship {
 }
 
 impl WorkbookXML {
-    fn get_defined_names_with_scope(&self) -> Vec<(String, Option)> {
+    fn get_defined_names_with_scope(&self) -> Vec {
         let sheet_id_index: Vec = self.worksheets.iter().map(|s| s.sheet_id).collect();
 
         let defined_names = self
@@ -58,7 +59,7 @@ impl WorkbookXML {
                     // convert Option to Option
                     .map(|pos| pos as u32);
 
-                (dn.name.clone(), index)
+                (dn.name.clone(), index, dn.formula.clone())
             })
             .collect::>();
         defined_names
@@ -304,12 +305,14 @@ fn from_a1_to_rc(
     worksheets: &[String],
     context: String,
     tables: HashMap,
-    defined_names: Vec<(String, Option)>,
+    defined_names: Vec,
 ) -> Result {
     let mut parser = Parser::new(worksheets.to_owned(), defined_names, tables);
     let cell_reference =
         parse_reference(&context).map_err(|error| XlsxError::Xml(error.to_string()))?;
-    let t = parser.parse(&formula, &cell_reference);
+    let mut t = parser.parse(&formula, &cell_reference);
+    add_implicit_intersection(&mut t, true);
+
     Ok(to_rc_format(&t))
 }
 
@@ -706,7 +709,7 @@ pub(super) fn load_sheet(
     worksheets: &[String],
     tables: &HashMap,
     shared_strings: &mut Vec,
-    defined_names: Vec<(String, Option)>,
+    defined_names: Vec,
 ) -> Result<(Worksheet, bool), XlsxError> {
     let sheet_name = &settings.name;
     let sheet_id = settings.id;
diff --git a/xlsx/tests/test.rs b/xlsx/tests/test.rs
index 5b34de6..0845da5 100644
--- a/xlsx/tests/test.rs
+++ b/xlsx/tests/test.rs
@@ -48,8 +48,8 @@ fn test_example() {
     assert_eq!(ws[0].views[&0].range, [13, 5, 20, 14]);
 
     let model2 = load_from_icalc("tests/example.ic").unwrap();
-    let s = bitcode::encode(&model2.workbook);
-    assert_eq!(workbook, model2.workbook, "{:?}", s);
+    let _ = bitcode::encode(&model2.workbook);
+    assert_eq!(workbook, model2.workbook);
 }
 
 #[test]
@@ -346,21 +346,31 @@ fn test_xlsx() {
     let path = format!("{}", Uuid::new_v4());
     let dir = temp_folder.join(path);
     fs::create_dir(&dir).unwrap();
+    let mut is_error = false;
     for file_path in entries {
         let file_name_str = file_path.file_name().unwrap().to_str().unwrap();
         let file_path_str = file_path.to_str().unwrap();
         println!("Testing file: {}", file_path_str);
         if file_name_str.ends_with(".xlsx") && !file_name_str.starts_with('~') {
             if let Err(message) = test_file(file_path_str) {
+                println!("Error with file: '{file_path_str}'");
                 println!("{}", message);
-                panic!("Model was evaluated inconsistently with XLSX data.")
+                is_error = true;
+            }
+            let t = test_load_and_saving(file_path_str, &dir);
+            if t.is_err() {
+                println!("Error while load and saving file: {file_path_str}");
+                is_error = true;
             }
-            assert!(test_load_and_saving(file_path_str, &dir).is_ok());
         } else {
             println!("skipping");
         }
     }
     fs::remove_dir_all(&dir).unwrap();
+    assert!(
+        !is_error,
+        "Models were evaluated inconsistently with XLSX data."
+    );
 }
 
 #[test]
@@ -375,20 +385,26 @@ fn no_export() {
     let path = format!("{}", Uuid::new_v4());
     let dir = temp_folder.join(path);
     fs::create_dir(&dir).unwrap();
+    let mut is_error = false;
     for file_path in entries {
         let file_name_str = file_path.file_name().unwrap().to_str().unwrap();
         let file_path_str = file_path.to_str().unwrap();
         println!("Testing file: {}", file_path_str);
         if file_name_str.ends_with(".xlsx") && !file_name_str.starts_with('~') {
             if let Err(message) = test_file(file_path_str) {
+                println!("Error with file: '{file_path_str}'");
                 println!("{}", message);
-                panic!("Model was evaluated inconsistently with XLSX data.")
+                is_error = true;
             }
         } else {
             println!("skipping");
         }
     }
     fs::remove_dir_all(&dir).unwrap();
+    assert!(
+        !is_error,
+        "Models were evaluated inconsistently with XLSX data."
+    );
 }
 
 // This test verifies whether exporting the merged cells functionality is happening properly or not.
@@ -476,6 +492,7 @@ fn test_documentation_xlsx() {
     let path = format!("{}", Uuid::new_v4());
     let dir = temp_folder.join(path);
     fs::create_dir(&dir).unwrap();
+    let mut is_error = false;
     for file_path in entries {
         let file_name_str = file_path.file_name().unwrap().to_str().unwrap();
         let file_path_str = file_path.to_str().unwrap();
@@ -487,7 +504,7 @@ fn test_documentation_xlsx() {
         if file_name_str.ends_with(".xlsx") && !file_name_str.starts_with('~') {
             if let Err(message) = test_file(file_path_str) {
                 println!("{}", message);
-                panic!("Model was evaluated inconsistently with XLSX data.")
+                is_error = true;
             }
             assert!(test_load_and_saving(file_path_str, &dir).is_ok());
         } else {
@@ -495,6 +512,10 @@ fn test_documentation_xlsx() {
         }
     }
     fs::remove_dir_all(&dir).unwrap();
+    assert!(
+        !is_error,
+        "Models were evaluated inconsistently with XLSX data."
+    )
 }
 
 #[test]