Add a standalone formula parser.

This is a fork of the EPPlus formula parser with all of the code that
evaluates formulas removed. AppSheet doesn't use this code as part of
its expression system, and removing it allows some significant
complexity and size reductions:

- Most of the dependencies on EPPlus Excel-specific code are
  unnecessary and have been removed
- Parsing can be a static method; there's no need for interfaces for
  everything
- There's no need for an IDisposable parser
- Many framework/NuGet dependencies are not necessary and have been
  removed

In addition, a significant amount of cleanup work has been done:
- Nullable reference types have been enabled
- Code has been generally simplified where possible
- Unnecessary interfaces have been removed

Note that this does *not* change the fundamental design or
functionality of the formula parser. Notably:
- Parsing behavior should be completely unchanged, down to whatever
  bugs the old system had
- NullReferenceException is still thrown for (some) malformed formulas
- The output is still a weird linked-list / tree hybrid
- Many functions that AppSheet doesn't support are still happily parsed

The idea is for this to be fully compatible with the existing
expression system, but faster and with fewer dependencies.

Change-Id: Ic687924a60b2673c24b39295ed684ed33d5cf9e4
Reviewed-on: https://gnocchi-internal-review.git.corp.google.com/c/third_party/epplus/+/208509
Reviewed-by: Mike Smith <mikeas@google.com>
Reviewed-by: Hughes Hilton <hugheshilton@google.com>
diff --git a/EPPlus.sln b/EPPlus.sln
index a545934..d838aaf 100644
--- a/EPPlus.sln
+++ b/EPPlus.sln
@@ -13,55 +13,26 @@
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetCoreTests", "NetCoreTests\NetCoreTests.csproj", "{7D59FAA6-A53F-4672-8387-9281731509F2}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpplusFormulaParser", "EpplusFormulaParser\EpplusFormulaParser.csproj", "{5411F24B-D58C-40F5-9300-E02C28154A4C}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
-		Debug|Mixed Platforms = Debug|Mixed Platforms
-		Debug|x86 = Debug|x86
-		Release 4.0|Any CPU = Release 4.0|Any CPU
-		Release 4.0|Mixed Platforms = Release 4.0|Mixed Platforms
-		Release 4.0|x86 = Release 4.0|x86
 		Release|Any CPU = Release|Any CPU
-		Release|Mixed Platforms = Release|Mixed Platforms
-		Release|x86 = Release|x86
 	EndGlobalSection
 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
 		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Debug|x86.Build.0 = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release 4.0|Any CPU.ActiveCfg = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release 4.0|Any CPU.Build.0 = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release 4.0|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release 4.0|Mixed Platforms.Build.0 = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release 4.0|x86.ActiveCfg = Debug|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release 4.0|x86.Build.0 = Debug|Any CPU
 		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release|Any CPU.Build.0 = Release|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release|x86.ActiveCfg = Release|Any CPU
-		{256597F1-67E5-478D-B5B8-3BBB47CB3D3F}.Release|x86.Build.0 = Release|Any CPU
 		{7D59FAA6-A53F-4672-8387-9281731509F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
 		{7D59FAA6-A53F-4672-8387-9281731509F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Debug|x86.ActiveCfg = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Debug|x86.Build.0 = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release 4.0|Any CPU.ActiveCfg = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release 4.0|Any CPU.Build.0 = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release 4.0|Mixed Platforms.ActiveCfg = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release 4.0|Mixed Platforms.Build.0 = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release 4.0|x86.ActiveCfg = Debug|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release 4.0|x86.Build.0 = Debug|Any CPU
 		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release|Any CPU.Build.0 = Release|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release|Mixed Platforms.Build.0 = Release|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release|x86.ActiveCfg = Release|Any CPU
-		{7D59FAA6-A53F-4672-8387-9281731509F2}.Release|x86.Build.0 = Release|Any CPU
+		{5411F24B-D58C-40F5-9300-E02C28154A4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{5411F24B-D58C-40F5-9300-E02C28154A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{5411F24B-D58C-40F5-9300-E02C28154A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{5411F24B-D58C-40F5-9300-E02C28154A4C}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/EpplusFormulaParser/EpplusFormulaParser.csproj b/EpplusFormulaParser/EpplusFormulaParser.csproj
new file mode 100644
index 0000000..366cd3d
--- /dev/null
+++ b/EpplusFormulaParser/EpplusFormulaParser.csproj
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <PackageId>Appsheet.EpplusFormulaParser</PackageId>
+    <Version>1.0.0</Version>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+</Project>
diff --git a/EpplusFormulaParser/Excel/Operators/IOperator.cs b/EpplusFormulaParser/Excel/Operators/IOperator.cs
new file mode 100644
index 0000000..31f9bff
--- /dev/null
+++ b/EpplusFormulaParser/Excel/Operators/IOperator.cs
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public interface IOperator {
+  OperatorType Type { get; }
+
+  int Precedence { get; }
+}
diff --git a/EpplusFormulaParser/Excel/Operators/Operator.cs b/EpplusFormulaParser/Excel/Operators/Operator.cs
new file mode 100644
index 0000000..f69a12f
--- /dev/null
+++ b/EpplusFormulaParser/Excel/Operators/Operator.cs
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+
+namespace EpplusFormulaParser;
+
+public class Operator : IOperator {
+  private const int _precedencePercent = 2;
+  private const int _precedenceExp = 4;
+  private const int _precedenceMultiplyDivide = 6;
+  private const int _precedenceAddSubtract = 12;
+  private const int _precedenceConcat = 15;
+  private const int _precedenceComparison = 25;
+
+  private Operator(OperatorType type, int precedence) {
+    Type = type;
+    Precedence = precedence;
+  }
+
+  public OperatorType Type { get; }
+
+  public int Precedence { get; }
+
+  public override string ToString() {
+    return "Operator: " + Type;
+  }
+
+  public static IOperator Plus { get; } = new Operator(OperatorType.Plus, _precedenceAddSubtract);
+
+  public static IOperator Minus { get; } = new Operator(OperatorType.Minus, _precedenceAddSubtract);
+
+  public static IOperator Multiply { get; } =
+      new Operator(OperatorType.Multiply, _precedenceMultiplyDivide);
+
+  public static IOperator Divide { get; } =
+      new Operator(OperatorType.Divide, _precedenceMultiplyDivide);
+
+  public static IOperator Exp { get; } = new Operator(OperatorType.Exponentiation, _precedenceExp);
+
+  public static IOperator Concat { get; } = new Operator(OperatorType.Concat, _precedenceConcat);
+
+  public static IOperator GreaterThan { get; } =
+      new Operator(OperatorType.GreaterThan, _precedenceComparison);
+
+  public static IOperator Eq { get; } = new Operator(OperatorType.Equals, _precedenceComparison);
+
+  public static IOperator NotEqualsTo { get; } =
+      new Operator(OperatorType.NotEqualTo, _precedenceComparison);
+
+  public static IOperator GreaterThanOrEqual { get; } =
+      new Operator(OperatorType.GreaterThanOrEqual, _precedenceComparison);
+
+  public static IOperator LessThan { get; } =
+      new Operator(OperatorType.LessThan, _precedenceComparison);
+
+  public static IOperator LessThanOrEqual { get; } =
+      new Operator(OperatorType.LessThanOrEqual, _precedenceComparison);
+
+  public static IOperator Percent { get; } = new Operator(OperatorType.Percent, _precedencePercent);
+
+  private static FrozenDictionary<string, IOperator> _fromString =
+      new Dictionary<string, IOperator> {
+        { "+", Plus },
+        { "-", Minus },
+        { "*", Multiply },
+        { "/", Divide },
+        { "^", Exp },
+        { "=", Eq },
+        { ">", GreaterThan },
+        { ">=", GreaterThanOrEqual },
+        { "<", LessThan },
+        { "<=", LessThanOrEqual },
+        { "<>", NotEqualsTo },
+        { "&", Concat },
+      }.ToFrozenDictionary();
+
+  public static bool TryParse(string opString, [NotNullWhen(true)] out IOperator? @operator) {
+    return _fromString.TryGetValue(opString, out @operator);
+  }
+}
diff --git a/EpplusFormulaParser/Excel/Operators/OperatorType.cs b/EpplusFormulaParser/Excel/Operators/OperatorType.cs
new file mode 100644
index 0000000..13a7dd3
--- /dev/null
+++ b/EpplusFormulaParser/Excel/Operators/OperatorType.cs
@@ -0,0 +1,51 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public enum OperatorType {
+  Undefined,
+  Concat,
+  Plus,
+  Minus,
+  Multiply,
+  Divide,
+  Modulus,
+  Percent,
+  Equals,
+  GreaterThan,
+  GreaterThanOrEqual,
+  LessThan,
+  LessThanOrEqual,
+  NotEqualTo,
+  IntegerDivision,
+  Exponentiation,
+}
diff --git a/EpplusFormulaParser/ExcelUtilities/ExcelAddressBase.cs b/EpplusFormulaParser/ExcelUtilities/ExcelAddressBase.cs
new file mode 100644
index 0000000..a9fe815
--- /dev/null
+++ b/EpplusFormulaParser/ExcelUtilities/ExcelAddressBase.cs
@@ -0,0 +1,206 @@
+using System.Collections.Frozen;
+using System.Collections.Generic;
+using System.Globalization;
+
+/// <summary>
+/// A range address
+/// </summary>
+/// <remarks>Examples of addresses are "A1" "B1:C2" "A:A" "1:1" "A1:E2,G3:G5" </remarks>
+internal static class ExcelAddressUtilities {
+  /// <summary>
+  /// Maximum number of columns in a worksheet (16384).
+  /// </summary>
+  private const int _maxColumns = 16384;
+
+  /// <summary>
+  /// Maximum number of rows in a worksheet (1048576).
+  /// </summary>
+  private const int _maxRows = 1048576;
+
+  private static readonly FrozenSet<char> _formulaChars = new HashSet<char> {
+    '(',
+    ')',
+    '+',
+    '-',
+    '*',
+    '/',
+    '.',
+    '=',
+    '^',
+    '&',
+    '%',
+    '\"',
+  }.ToFrozenSet();
+
+  public enum AddressType {
+    Invalid,
+    InternalAddress,
+    ExternalAddress,
+    InternalName,
+    ExternalName,
+    Formula,
+  }
+
+  public static AddressType IsValid(string address) {
+    if (address == "#REF!") {
+      return AddressType.Invalid;
+    }
+    if (double.TryParse(
+        address,
+        NumberStyles.Any,
+        CultureInfo.InvariantCulture,
+        out _)) //A double, no valid address
+    {
+      return AddressType.Invalid;
+    }
+    if (IsFormula(address)) {
+      return AddressType.Formula;
+    }
+    if (SplitAddress(address, out var wb, out var intAddress)) {
+      if (intAddress.Contains(
+          '[')) //Table reference
+      {
+        return string.IsNullOrEmpty(wb) ? AddressType.InternalAddress : AddressType.ExternalAddress;
+      }
+      if (intAddress.Contains(',')) {
+        intAddress = intAddress[..intAddress.IndexOf(',')];
+      }
+      if (IsAddress(intAddress)) {
+        return string.IsNullOrEmpty(wb) ? AddressType.InternalAddress : AddressType.ExternalAddress;
+      }
+      return string.IsNullOrEmpty(wb) ? AddressType.InternalName : AddressType.ExternalName;
+    }
+    return AddressType.Invalid;
+  }
+
+  private static bool GetRowCol(string address, out int row, out int col, bool throwException) {
+    var colPart = true;
+    var colStartIx = 0;
+    var colLength = 0;
+    col = 0;
+    row = 0;
+
+    if (address.EndsWith("#REF!")) {
+      row = 0;
+      col = 0;
+      return true;
+    }
+
+    var sheetMarkerIndex = address.IndexOf('!');
+    if (sheetMarkerIndex >= 0) {
+      colStartIx = sheetMarkerIndex + 1;
+    }
+    address = address.ToUpper(CultureInfo.InvariantCulture);
+    for (var i = colStartIx; i < address.Length; i++) {
+      var c = address[i];
+      if (colPart && c >= 'A' && c <= 'Z' && colLength <= 3) {
+        col *= 26;
+        col += c - 64;
+        colLength++;
+      } else if (c >= '0' && c <= '9') {
+        row *= 10;
+        row += c - 48;
+        colPart = false;
+      } else if (c == '$') {
+        if (i == colStartIx) {
+          colStartIx++;
+        } else {
+          colPart = false;
+        }
+      } else {
+        row = 0;
+        col = 0;
+        if (throwException) {
+          throw new($"Invalid Address format {address}");
+        }
+        return false;
+      }
+    }
+    return row != 0 || col != 0;
+  }
+
+  private static bool IsAddress(string intAddress) {
+    if (string.IsNullOrEmpty(intAddress)) {
+      return false;
+    }
+    var cells = intAddress.Split(':');
+    int toRow,
+        toCol;
+
+    if (!GetRowCol(cells[0], out var fromRow, out var fromCol, false)) {
+      return false;
+    }
+    if (cells.Length > 1) {
+      if (!GetRowCol(cells[1], out toRow, out toCol, false)) {
+        return false;
+      }
+    } else {
+      toRow = fromRow;
+      toCol = fromCol;
+    }
+    return fromRow <= toRow
+        && fromCol <= toCol
+        && fromCol > -1
+        && toCol <= _maxColumns
+        && fromRow > -1
+        && toRow <= _maxRows;
+  }
+
+  private static bool SplitAddress(string address, out string wb, out string intAddress) {
+    wb = "";
+    intAddress = "";
+    var text = "";
+    var isText = false;
+    var brackPos = -1;
+    for (var i = 0; i < address.Length; i++) {
+      if (address[i] == '\'') {
+        isText = !isText;
+        if (i > 0 && address[i - 1] == '\'') {
+          text += "'";
+        }
+      } else {
+        if (address[i] == '!' && !isText) {
+          if (text.Length > 0 && text[0] == '[') {
+            wb = text.Substring(1, text.IndexOf(']') - 1);
+          }
+          intAddress = address[(i + 1)..];
+          return true;
+        }
+        if (address[i] == '[' && !isText) {
+          if (i
+              > 0) //Table reference return full address;
+          {
+            intAddress = address;
+            return true;
+          }
+          brackPos = i;
+        } else if (address[i] == ']' && !isText) {
+          if (brackPos > -1) {
+            wb = text;
+            text = "";
+          } else {
+            return false;
+          }
+        } else {
+          text += address[i];
+        }
+      }
+    }
+    intAddress = text;
+    return true;
+  }
+
+  private static bool IsFormula(string address) {
+    var isText = false;
+    foreach (var c in address) {
+      if (c == '\'') {
+        isText = !isText;
+      } else {
+        if (isText == false && _formulaChars.Contains(c)) {
+          return true;
+        }
+      }
+    }
+    return false;
+  }
+}
diff --git a/EpplusFormulaParser/Exceptions/ExcelErrorValueException.cs b/EpplusFormulaParser/Exceptions/ExcelErrorValueException.cs
new file mode 100644
index 0000000..a7c9a8e
--- /dev/null
+++ b/EpplusFormulaParser/Exceptions/ExcelErrorValueException.cs
@@ -0,0 +1,5 @@
+using System;
+
+namespace EpplusFormulaParser;
+
+public class ExcelErrorValueException : Exception;
diff --git a/EpplusFormulaParser/Exceptions/UnrecognizedTokenException.cs b/EpplusFormulaParser/Exceptions/UnrecognizedTokenException.cs
new file mode 100644
index 0000000..ce20e84
--- /dev/null
+++ b/EpplusFormulaParser/Exceptions/UnrecognizedTokenException.cs
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System;
+
+namespace EpplusFormulaParser;
+
+public class UnrecognizedTokenException(Token token)
+    : Exception("Unrecognized token: " + token.Value);
diff --git a/EpplusFormulaParser/ExpressionGraph/AtomicExpression.cs b/EpplusFormulaParser/ExpressionGraph/AtomicExpression.cs
new file mode 100644
index 0000000..4fb567c
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/AtomicExpression.cs
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public abstract class AtomicExpression(string expression) : Expression(expression) {
+  public override bool IsGroupedExpression => false;
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/BooleanExpression.cs b/EpplusFormulaParser/ExpressionGraph/BooleanExpression.cs
new file mode 100644
index 0000000..8cc1146
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/BooleanExpression.cs
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class BooleanExpression(string expression) : AtomicExpression(expression);
diff --git a/EpplusFormulaParser/ExpressionGraph/ConstantExpressions.cs b/EpplusFormulaParser/ExpressionGraph/ConstantExpressions.cs
new file mode 100644
index 0000000..63b8f23
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/ConstantExpressions.cs
@@ -0,0 +1,7 @@
+namespace EpplusFormulaParser;
+
+public static class ConstantExpressions {
+  public static Expression Percent => new ConstantExpression("Percent");
+}
+
+public class ConstantExpression(string title) : AtomicExpression(title);
diff --git a/EpplusFormulaParser/ExpressionGraph/DecimalExpression.cs b/EpplusFormulaParser/ExpressionGraph/DecimalExpression.cs
new file mode 100644
index 0000000..704308b
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/DecimalExpression.cs
@@ -0,0 +1,46 @@
+using System.Globalization;
+
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class DecimalExpression(string expression, bool negate) : AtomicExpression(expression) {
+  public double Compile() {
+    var result = double.Parse(ExpressionString, CultureInfo.InvariantCulture);
+    if (negate) {
+      result = -result;
+    }
+    return result;
+  }
+
+  public bool IsNegated => negate;
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/EnumerableExpression.cs b/EpplusFormulaParser/ExpressionGraph/EnumerableExpression.cs
new file mode 100644
index 0000000..29367f1
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/EnumerableExpression.cs
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class EnumerableExpression : Expression {
+  public override bool IsGroupedExpression => false;
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/ExcelAddressExpression.cs b/EpplusFormulaParser/ExpressionGraph/ExcelAddressExpression.cs
new file mode 100644
index 0000000..f58b294
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/ExcelAddressExpression.cs
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class ExcelAddressExpression(string expression, bool negate) : AtomicExpression(expression) {
+  public override bool IsGroupedExpression => false;
+
+  public bool IsNegated => negate;
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/ExcelErrorExpression.cs b/EpplusFormulaParser/ExpressionGraph/ExcelErrorExpression.cs
new file mode 100644
index 0000000..95cc139
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/ExcelErrorExpression.cs
@@ -0,0 +1,36 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class ExcelErrorExpression(string expression) : Expression(expression) {
+  public override bool IsGroupedExpression => false;
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/Expression.cs b/EpplusFormulaParser/ExpressionGraph/Expression.cs
new file mode 100644
index 0000000..506469a
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/Expression.cs
@@ -0,0 +1,78 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EpplusFormulaParser;
+
+public abstract class Expression {
+  public string ExpressionString { get; private set; }
+
+  private readonly List<Expression> _children;
+
+  public IReadOnlyList<Expression> Children { get; }
+
+  public Expression? Next { get; set; }
+
+  public Expression? Prev { get; set; }
+
+  public IOperator? Operator { get; set; }
+
+  public abstract bool IsGroupedExpression { get; }
+
+  protected Expression()
+      : this("") {}
+
+  protected Expression(string expression) {
+    ExpressionString = expression;
+    _children = [];
+    Children = _children.AsReadOnly();
+  }
+
+  public virtual bool ParentIsLookupFunction { get; set; }
+
+  public virtual bool HasChildren => _children.Count != 0;
+
+  internal virtual Expression PrepareForNextChild() {
+    return this;
+  }
+
+  public virtual Expression AddChild(Expression child) {
+    if (_children.Count != 0) {
+      var last = _children.Last();
+      child.Prev = last;
+      last.Next = child;
+    }
+    _children.Add(child);
+    return child;
+  }
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/ExpressionFactory.cs b/EpplusFormulaParser/ExpressionGraph/ExpressionFactory.cs
new file mode 100644
index 0000000..4ce661a
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/ExpressionFactory.cs
@@ -0,0 +1,61 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+internal static class ExpressionFactory {
+  public static Expression Create(Token token) {
+    switch (token.TokenType) {
+      case TokenType.Integer:
+        return new IntegerExpression(token.Value, token.IsNegated);
+      case TokenType.String:
+        return new StringExpression(token.Value);
+      case TokenType.Decimal:
+        return new DecimalExpression(token.Value, token.IsNegated);
+      case TokenType.Boolean:
+        return new BooleanExpression(token.Value);
+      case TokenType.ExcelAddress:
+        return new ExcelAddressExpression(token.Value, token.IsNegated);
+      case TokenType.InvalidReference:
+        return new ExcelErrorExpression(token.Value);
+      case TokenType.NumericError:
+        return new ExcelErrorExpression(token.Value);
+      case TokenType.ValueDataTypeError:
+        return new ExcelErrorExpression(token.Value);
+      case TokenType.Null:
+        return new ExcelErrorExpression(token.Value);
+      case TokenType.NameValue:
+        return new NamedValueExpression(token.Value);
+      default:
+        return new StringExpression(token.Value);
+    }
+  }
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/ExpressionGraph.cs b/EpplusFormulaParser/ExpressionGraph/ExpressionGraph.cs
new file mode 100644
index 0000000..100e186
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/ExpressionGraph.cs
@@ -0,0 +1,56 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Collections.Generic;
+
+namespace EpplusFormulaParser;
+
+public sealed class ExpressionGraph {
+  private readonly List<Expression> _expressions;
+
+  public IReadOnlyList<Expression> Expressions { get; }
+
+  public Expression? Current { get; private set; }
+
+  internal ExpressionGraph() {
+    _expressions = [];
+    Expressions = _expressions.AsReadOnly();
+  }
+
+  internal void Add(Expression expression) {
+    _expressions.Add(expression);
+    if (Current != null) {
+      Current.Next = expression;
+      expression.Prev = Current;
+    }
+    Current = expression;
+  }
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/ExpressionGraphBuilder.cs b/EpplusFormulaParser/ExpressionGraph/ExpressionGraphBuilder.cs
new file mode 100644
index 0000000..6d6b711
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/ExpressionGraphBuilder.cs
@@ -0,0 +1,174 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EpplusFormulaParser;
+
+internal sealed class ExpressionGraphBuilder {
+  private readonly ExpressionGraph _graph = new();
+  private int _tokenIndex;
+  private bool _negateNextExpression;
+
+  public ExpressionGraph Build(List<Token> tokens) {
+    _tokenIndex = 0;
+    BuildUp(tokens, null);
+    return _graph;
+  }
+
+  private void BuildUp(List<Token> tokens, Expression? parent) {
+    while (_tokenIndex < tokens.Count) {
+      var token = tokens[_tokenIndex];
+      if (token.TokenType == TokenType.Operator && Operator.TryParse(token.Value, out var op)) {
+        SetOperatorOnExpression(parent, op);
+      } else if (token.TokenType == TokenType.Function) {
+        BuildFunctionExpression(tokens, parent, token.Value);
+      } else if (token.TokenType == TokenType.OpeningEnumerable) {
+        _tokenIndex++;
+        BuildEnumerableExpression(tokens, parent);
+      } else if (token.TokenType == TokenType.OpeningParenthesis) {
+        _tokenIndex++;
+        BuildGroupExpression(tokens, parent);
+      } else if (token.TokenType == TokenType.ClosingParenthesis
+          || token.TokenType == TokenType.ClosingEnumerable) {
+        break;
+      } else if (token.TokenType == TokenType.Negator) {
+        _negateNextExpression = true;
+      } else if (token.TokenType == TokenType.Percent) {
+        SetOperatorOnExpression(parent, Operator.Percent);
+        if (parent == null) {
+          _graph.Add(ConstantExpressions.Percent);
+        } else {
+          parent.AddChild(ConstantExpressions.Percent);
+        }
+      } else {
+        CreateAndAppendExpression(ref parent, token);
+      }
+      _tokenIndex++;
+    }
+  }
+
+  private void BuildEnumerableExpression(List<Token> tokens, Expression? parent) {
+    if (parent == null) {
+      _graph.Add(new EnumerableExpression());
+      BuildUp(tokens, _graph.Current);
+    } else {
+      var enumerableExpression = new EnumerableExpression();
+      parent.AddChild(enumerableExpression);
+      BuildUp(tokens, enumerableExpression);
+    }
+  }
+
+  private void CreateAndAppendExpression(ref Expression? parent, Token token) {
+    if (IsWaste(token)) {
+      return;
+    }
+    if (parent != null
+        && (token.TokenType == TokenType.Comma || token.TokenType == TokenType.SemiColon)) {
+      parent = parent.PrepareForNextChild();
+      return;
+    }
+    if (_negateNextExpression) {
+      token.Negate();
+      _negateNextExpression = false;
+    }
+    var expression = ExpressionFactory.Create(token);
+    if (parent == null) {
+      _graph.Add(expression);
+    } else {
+      parent.AddChild(expression);
+    }
+  }
+
+  private bool IsWaste(Token token) {
+    if (token.TokenType == TokenType.String) {
+      return true;
+    }
+    return false;
+  }
+
+  private void BuildFunctionExpression(List<Token> tokens, Expression? parent, string funcName) {
+    if (parent == null) {
+      _graph.Add(new FunctionExpression(funcName));
+      _negateNextExpression = false;
+      HandleFunctionArguments(tokens, _graph.Current!);
+    } else {
+      var func = new FunctionExpression(funcName);
+      _negateNextExpression = false;
+      parent.AddChild(func);
+      HandleFunctionArguments(tokens, func);
+    }
+  }
+
+  private void HandleFunctionArguments(List<Token> tokens, Expression function) {
+    _tokenIndex++;
+    var token = tokens.ElementAt(_tokenIndex);
+    if (token.TokenType != TokenType.OpeningParenthesis) {
+      throw new ExcelErrorValueException();
+    }
+    _tokenIndex++;
+    BuildUp(tokens, function.Children.First());
+  }
+
+  private void BuildGroupExpression(List<Token> tokens, Expression? parent) {
+    if (parent == null) {
+      _graph.Add(new GroupExpression(_negateNextExpression));
+      _negateNextExpression = false;
+      BuildUp(tokens, _graph.Current);
+    } else {
+      if (parent.IsGroupedExpression || parent is FunctionArgumentExpression) {
+        var newGroupExpression = new GroupExpression(_negateNextExpression);
+        _negateNextExpression = false;
+        parent.AddChild(newGroupExpression);
+        BuildUp(tokens, newGroupExpression);
+      }
+      BuildUp(tokens, parent);
+    }
+  }
+
+  private void SetOperatorOnExpression(Expression? parent, IOperator op) {
+    if (parent == null) {
+      _graph.Current!.Operator = op;
+    } else {
+      Expression candidate;
+      if (parent is FunctionArgumentExpression) {
+        candidate = parent.Children.Last();
+      } else {
+        candidate = parent.Children.Last();
+        if (candidate is FunctionArgumentExpression) {
+          candidate = candidate.Children.Last();
+        }
+      }
+      candidate.Operator = op;
+    }
+  }
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/FunctionArgumentExpression.cs b/EpplusFormulaParser/ExpressionGraph/FunctionArgumentExpression.cs
new file mode 100644
index 0000000..ab9e322
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/FunctionArgumentExpression.cs
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public sealed class FunctionArgumentExpression(Expression function) : GroupExpression(false) {
+  public override bool ParentIsLookupFunction {
+    get => base.ParentIsLookupFunction;
+    set {
+      base.ParentIsLookupFunction = value;
+      foreach (var child in Children) {
+        child.ParentIsLookupFunction = value;
+      }
+    }
+  }
+
+  public override bool IsGroupedExpression => false;
+
+  internal override Expression PrepareForNextChild() {
+    return function.PrepareForNextChild();
+  }
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/FunctionExpression.cs b/EpplusFormulaParser/ExpressionGraph/FunctionExpression.cs
new file mode 100644
index 0000000..19c5c57
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/FunctionExpression.cs
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Linq;
+
+namespace EpplusFormulaParser;
+
+public class FunctionExpression : AtomicExpression {
+  public FunctionExpression(string expression)
+      : base(expression) {
+    base.AddChild(new FunctionArgumentExpression(this));
+  }
+
+  internal override Expression PrepareForNextChild() {
+    return base.AddChild(new FunctionArgumentExpression(this));
+  }
+
+  public override bool HasChildren => Children.Any() && Children[0].Children.Any();
+
+  public override Expression AddChild(Expression child) {
+    Children[^1].AddChild(child);
+    return child;
+  }
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/GroupExpression.cs b/EpplusFormulaParser/ExpressionGraph/GroupExpression.cs
new file mode 100644
index 0000000..7f184c0
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/GroupExpression.cs
@@ -0,0 +1,38 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class GroupExpression(bool isNegated) : Expression {
+  public override bool IsGroupedExpression => true;
+
+  public bool IsNegated => isNegated;
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/IntegerExpression.cs b/EpplusFormulaParser/ExpressionGraph/IntegerExpression.cs
new file mode 100644
index 0000000..e1f9f7b
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/IntegerExpression.cs
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Globalization;
+
+namespace EpplusFormulaParser;
+
+public class IntegerExpression(string expression, bool negate) : AtomicExpression(expression) {
+  public double Compile() {
+    var result = double.Parse(ExpressionString, CultureInfo.InvariantCulture);
+    if (negate) {
+      result = -result;
+    }
+    return result;
+  }
+
+  public bool IsNegated => negate;
+}
diff --git a/EpplusFormulaParser/ExpressionGraph/NamedValueExpression.cs b/EpplusFormulaParser/ExpressionGraph/NamedValueExpression.cs
new file mode 100644
index 0000000..c0fee75
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/NamedValueExpression.cs
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class NamedValueExpression(string expression) : AtomicExpression(expression);
diff --git a/EpplusFormulaParser/ExpressionGraph/StringExpression.cs b/EpplusFormulaParser/ExpressionGraph/StringExpression.cs
new file mode 100644
index 0000000..9d452d8
--- /dev/null
+++ b/EpplusFormulaParser/ExpressionGraph/StringExpression.cs
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class StringExpression(string expression) : AtomicExpression(expression);
diff --git a/EpplusFormulaParser/FormulaParser.cs b/EpplusFormulaParser/FormulaParser.cs
new file mode 100644
index 0000000..84f2a1e
--- /dev/null
+++ b/EpplusFormulaParser/FormulaParser.cs
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public static class FormulaParser {
+  public static ExpressionGraph ParseToGraph(string formula) {
+    var tokens = Lexer.Tokenize(formula);
+    var expressionGraphBuilder = new ExpressionGraphBuilder();
+    var graph = expressionGraphBuilder.Build(tokens);
+    return graph;
+  }
+}
diff --git a/EpplusFormulaParser/FunctionRepository.cs b/EpplusFormulaParser/FunctionRepository.cs
new file mode 100644
index 0000000..c096cb8
--- /dev/null
+++ b/EpplusFormulaParser/FunctionRepository.cs
@@ -0,0 +1,192 @@
+/* Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ *******************************************************************************
+ * Mats Alm   		                Added		                2013-12-03
+ *******************************************************************************/
+
+using System;
+using System.Collections.Frozen;
+using System.Collections.Generic;
+
+namespace EpplusFormulaParser;
+
+internal static class FunctionRepository {
+  private static readonly FrozenSet<string> _functions = new HashSet<string>(
+      StringComparer.InvariantCultureIgnoreCase) {
+    // Text
+    "len",
+    "lower",
+    "upper",
+    "left",
+    "right",
+    "mid",
+    "replace",
+    "rept",
+    "substitute",
+    "concatenate",
+    "char",
+    "exact",
+    "find",
+    "fixed",
+    "proper",
+    "text",
+    "t",
+    "hyperlink",
+    // Numbers
+    "int",
+    // Math
+    "abs",
+    "asin",
+    "asinh",
+    "cos",
+    "cosh",
+    "power",
+    "sign",
+    "sqrt",
+    "sqrtpi",
+    "pi",
+    "product",
+    "ceiling",
+    "count",
+    "counta",
+    "countblank",
+    "countif",
+    "countifs",
+    "fact",
+    "floor",
+    "sin",
+    "sinh",
+    "sum",
+    "sumif",
+    "sumifs",
+    "sumproduct",
+    "sumsq",
+    "stdev",
+    "stdevp",
+    "stdev.s",
+    "stdev.p",
+    "subtotal",
+    "exp",
+    "log",
+    "log10",
+    "ln",
+    "max",
+    "maxa",
+    "median",
+    "min",
+    "mina",
+    "mod",
+    "average",
+    "averagea",
+    "averageif",
+    "averageifs",
+    "round",
+    "rounddown",
+    "roundup",
+    "rand",
+    "randbetween",
+    "quotient",
+    "trunc",
+    "tan",
+    "tanh",
+    "atan",
+    "atan2",
+    "atanh",
+    "acos",
+    "acosh",
+    "var",
+    "varp",
+    "large",
+    "small",
+    "degrees",
+    // Information
+    "isblank",
+    "isnumber",
+    "istext",
+    "isnontext",
+    "iserror",
+    "iserr",
+    "error.type",
+    "iseven",
+    "isodd",
+    "islogical",
+    "isna",
+    "na",
+    "n",
+    // Logical
+    "if",
+    "iferror",
+    "ifna",
+    "not",
+    "and",
+    "or",
+    "true",
+    "false",
+    // Reference and lookup
+    "address",
+    "hlookup",
+    "vlookup",
+    "lookup",
+    "match",
+    "row",
+    "rows",
+    "column",
+    "columns",
+    "choose",
+    "index",
+    "indirect",
+    "offset",
+    // Date
+    "date",
+    "today",
+    "now",
+    "day",
+    "month",
+    "year",
+    "time",
+    "hour",
+    "minute",
+    "second",
+    "weeknum",
+    "weekday",
+    "days360",
+    "yearfrac",
+    "edate",
+    "eomonth",
+    "isoweeknum",
+    "workday",
+    // Database
+    "dget",
+    "dcount",
+    "dcounta",
+    "dmax",
+    "dmin",
+    "dsum",
+    "daverage",
+    "dvar",
+    "dvarp",
+  }.ToFrozenSet();
+
+  public static bool IsFunctionName(string name) {
+    return _functions.Contains(name);
+  }
+}
diff --git a/EpplusFormulaParser/LexicalAnalysis/Lexer.cs b/EpplusFormulaParser/LexicalAnalysis/Lexer.cs
new file mode 100644
index 0000000..979b42e
--- /dev/null
+++ b/EpplusFormulaParser/LexicalAnalysis/Lexer.cs
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Collections.Generic;
+
+namespace EpplusFormulaParser;
+
+internal static class Lexer {
+  public static List<Token> Tokenize(string input) {
+    var tokens = SourceCodeTokenizer.Tokenize(input);
+    SyntacticAnalyzer.Analyze(tokens);
+    return tokens;
+  }
+}
diff --git a/EpplusFormulaParser/LexicalAnalysis/SourceCodeTokenizer.cs b/EpplusFormulaParser/LexicalAnalysis/SourceCodeTokenizer.cs
new file mode 100644
index 0000000..08c1c37
--- /dev/null
+++ b/EpplusFormulaParser/LexicalAnalysis/SourceCodeTokenizer.cs
@@ -0,0 +1,269 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Text.RegularExpressions;
+
+namespace EpplusFormulaParser;
+
+internal static partial class SourceCodeTokenizer {
+  public static List<Token> Tokenize(string input) {
+    if (string.IsNullOrEmpty(input)) {
+      return [];
+    }
+    // MA 1401: Ignore leading plus in formula.
+    input = input.TrimStart('+');
+    var context = new TokenizerContext(input);
+
+    var isSingleQuoteString = false;
+    for (var i = 0; i < context.Formula.Length; i++) {
+      var c = context.Formula[i];
+      if (CharIsTokenSeparator(c, out var tokenSeparator)) {
+        if (context.IsInString) {
+          if (IsDoubleQuote(tokenSeparator, i, context)) {
+            i++;
+            context.AppendToCurrentToken(c);
+            continue;
+          }
+          if (tokenSeparator.TokenType != TokenType.String) {
+            context.AppendToCurrentToken(c);
+            continue;
+          }
+          // CHANGE 2
+          if ((isSingleQuoteString && c != '\'') || (!isSingleQuoteString && c != '"')) {
+            context.AppendToCurrentToken(c);
+            continue;
+          }
+        }
+        if (tokenSeparator.TokenType == TokenType.OpeningBracket) {
+          context.AppendToCurrentToken(c);
+          context.BracketCount++;
+          continue;
+        }
+        if (tokenSeparator.TokenType == TokenType.ClosingBracket) {
+          context.AppendToCurrentToken(c);
+          context.BracketCount--;
+          continue;
+        }
+        if (context.BracketCount > 0) {
+          context.AppendToCurrentToken(c);
+          continue;
+        }
+        // two operators in sequence could be "<=" or ">="
+        if (IsPartOfMultipleCharSeparator(context, c)) {
+          var sOp = context.LastToken!.Value + c.ToString(CultureInfo.InvariantCulture);
+          var op = TokenSeparatorProvider.Tokens[sOp];
+          context.ReplaceLastToken(op);
+          context.NewToken();
+          continue;
+        }
+        if (tokenSeparator.TokenType == TokenType.String) {
+          // CHANGE3 :
+          isSingleQuoteString = c == '\'';
+          if (context.LastToken is { TokenType: TokenType.OpeningEnumerable }) {
+            // context.AppendToCurrentToken(c);  // Praveen's change of 10/28/2015
+            context.ToggleIsInString();
+            continue;
+          }
+          if (context.LastToken is { TokenType: TokenType.String }) {
+            context.AddToken(
+                !context.CurrentTokenHasValue
+                    ? new(string.Empty, TokenType.StringContent)
+                    : new Token(context.CurrentToken, TokenType.StringContent));
+          }
+          context.AddToken(new("\"", TokenType.String));
+          context.ToggleIsInString();
+          context.NewToken();
+          continue;
+        }
+        if (context.CurrentTokenHasValue) {
+          if (StringRegex().IsMatch(context.CurrentToken)) {
+            context.AddToken(new(context.CurrentToken, TokenType.StringContent));
+          } else {
+            context.AddToken(CreateToken(context));
+          }
+
+          //If the next token is an opening parantheses and the previous token is interpeted as an address or name, then the current token is a function
+          if (tokenSeparator.TokenType == TokenType.OpeningParenthesis
+              && context.LastToken?.TokenType is TokenType.ExcelAddress or TokenType.NameValue) {
+            context.LastToken.TokenType = TokenType.Function;
+          }
+        }
+        if (tokenSeparator.Value == "-") {
+          if (TokenIsNegator(context)) {
+            context.AddToken(new("-", TokenType.Negator));
+            continue;
+          }
+        }
+        context.AddToken(tokenSeparator);
+        context.NewToken();
+        continue;
+      }
+      context.AppendToCurrentToken(c);
+    }
+    if (context.CurrentTokenHasValue) {
+      context.AddToken(CreateToken(context));
+    }
+
+    CleanupTokens(context);
+
+    return context.Result;
+  }
+
+  private static bool IsDoubleQuote(
+      Token tokenSeparator,
+      int formulaCharIndex,
+      TokenizerContext context) {
+    return tokenSeparator.TokenType == TokenType.String
+        && formulaCharIndex + 1 < context.Formula.Length
+        && context.Formula[formulaCharIndex + 1] == '\"';
+  }
+
+  private static void CleanupTokens(TokenizerContext context) {
+    for (var i = 0; i < context.Result.Count; i++) {
+      var token = context.Result[i];
+      if (token.TokenType == TokenType.Unrecognized) {
+        if (i < context.Result.Count - 1) {
+          if (context.Result[i + 1].TokenType == TokenType.OpeningParenthesis) {
+            token.TokenType = TokenType.Function;
+          } else {
+            token.TokenType = TokenType.NameValue;
+          }
+        } else {
+          token.TokenType = TokenType.NameValue;
+        }
+      } else if (token.TokenType == TokenType.Function) {
+        if (i < context.Result.Count - 1) {
+          if (context.Result[i + 1].TokenType == TokenType.OpeningParenthesis) {
+            token.TokenType = TokenType.Function;
+          } else {
+            token.TokenType = TokenType.Unrecognized;
+          }
+        } else {
+          token.TokenType = TokenType.Unrecognized;
+        }
+      } else if ((token.TokenType == TokenType.Operator || token.TokenType == TokenType.Negator)
+          && i < context.Result.Count - 1
+          && (token.Value == "+" || token.Value == "-")) {
+        if (i > 0
+            && token.Value
+                == "+") //Remove any + with an opening parenthesis before.
+        {
+          if (context.Result[i - 1].TokenType == TokenType.OpeningParenthesis) {
+            context.Result.RemoveAt(i);
+            SetNegatorOperator(context, i);
+            i--;
+            continue;
+          }
+        }
+
+        var nextToken = context.Result[i + 1];
+        if (nextToken.TokenType == TokenType.Operator || nextToken.TokenType == TokenType.Negator) {
+          if (token.Value == "+" && (nextToken.Value == "+" || nextToken.Value == "-")) {
+            //Remove first
+            context.Result.RemoveAt(i);
+            SetNegatorOperator(context, i);
+            i--;
+          } else if (token.Value == "-" && nextToken.Value == "+") {
+            //Remove second
+            context.Result.RemoveAt(i + 1);
+            SetNegatorOperator(context, i);
+            i--;
+          } else if (token.Value == "-" && nextToken.Value == "-") {
+            //Remove first and set operator to +
+            context.Result.RemoveAt(i);
+            if (i == 0) {
+              context.Result.RemoveAt(i + 1);
+              i += 2;
+            } else {
+              //context.Result[i].TokenType = TokenType.Operator;
+              //context.Result[i].Value = "+";
+              context.Result[i] = TokenSeparatorProvider.Tokens["+"];
+              SetNegatorOperator(context, i);
+              i--;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private static void SetNegatorOperator(TokenizerContext context, int i) {
+    if (context.Result[i].Value == "-"
+        && i > 0
+        && (context.Result[i].TokenType == TokenType.Operator
+                || context.Result[i].TokenType == TokenType.Negator)) {
+      if (TokenIsNegator(context.Result[i - 1])) {
+        context.Result[i] = new("-", TokenType.Negator);
+      } else {
+        context.Result[i] = TokenSeparatorProvider.Tokens["-"];
+      }
+    }
+  }
+
+  private static bool TokenIsNegator(TokenizerContext context) {
+    return TokenIsNegator(context.LastToken);
+  }
+
+  private static bool TokenIsNegator(Token? t) {
+    return t == null
+        || t.TokenType == TokenType.Operator
+        || t.TokenType == TokenType.OpeningParenthesis
+        || t.TokenType == TokenType.Comma
+        || t.TokenType == TokenType.SemiColon
+        || t.TokenType == TokenType.OpeningEnumerable;
+  }
+
+  private static bool IsPartOfMultipleCharSeparator(TokenizerContext context, char c) {
+    var lastToken = context.LastToken != null ? context.LastToken.Value : string.Empty;
+    return TokenSeparatorProvider.IsOperator(lastToken)
+        && TokenSeparatorProvider.IsPossibleLastPartOfMultipleCharOperator(
+            c.ToString(CultureInfo.InvariantCulture))
+        && !context.CurrentTokenHasValue;
+  }
+
+  private static Token CreateToken(TokenizerContext context) {
+    if (context is { CurrentToken: "-", LastToken: null }) {
+      throw new NullReferenceException();
+    }
+    return TokenFactory.Create(context.Result, context.CurrentToken);
+  }
+
+  private static bool CharIsTokenSeparator(char c, [NotNullWhen(true)] out Token? token) {
+    return TokenSeparatorProvider.Tokens.TryGetValue(c.ToString(), out token);
+  }
+
+  [GeneratedRegex("^\"*$")]
+  private static partial Regex StringRegex();
+}
diff --git a/EpplusFormulaParser/LexicalAnalysis/SyntacticAnalyzer.cs b/EpplusFormulaParser/LexicalAnalysis/SyntacticAnalyzer.cs
new file mode 100644
index 0000000..2d698b7
--- /dev/null
+++ b/EpplusFormulaParser/LexicalAnalysis/SyntacticAnalyzer.cs
@@ -0,0 +1,88 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System;
+using System.Collections.Generic;
+
+namespace EpplusFormulaParser;
+
+internal static class SyntacticAnalyzer {
+  private ref struct AnalyzingContext {
+    public int NumberOfOpenedParentheses { get; set; }
+
+    public int NumberOfClosedParentheses { get; set; }
+
+    public int OpenedStrings { get; set; }
+
+    public int ClosedStrings { get; set; }
+
+    public bool IsInString { get; set; }
+  }
+
+  public static void Analyze(IEnumerable<Token> tokens) {
+    var context = new AnalyzingContext();
+    foreach (var token in tokens) {
+      if (token.TokenType == TokenType.Unrecognized) {
+        throw new UnrecognizedTokenException(token);
+      }
+      EnsureParenthesesAreWellFormed(token, ref context);
+      EnsureStringsAreWellFormed(token, ref context);
+    }
+    Validate(ref context);
+  }
+
+  private static void Validate(ref AnalyzingContext context) {
+    if (context.NumberOfOpenedParentheses != context.NumberOfClosedParentheses) {
+      throw new FormatException("Number of opened and closed parentheses does not match");
+    }
+    if (context.OpenedStrings != context.ClosedStrings) {
+      throw new FormatException("Unterminated string");
+    }
+  }
+
+  private static void EnsureParenthesesAreWellFormed(Token token, ref AnalyzingContext context) {
+    if (token.TokenType == TokenType.OpeningParenthesis) {
+      context.NumberOfOpenedParentheses++;
+    } else if (token.TokenType == TokenType.ClosingParenthesis) {
+      context.NumberOfClosedParentheses++;
+    }
+  }
+
+  private static void EnsureStringsAreWellFormed(Token token, ref AnalyzingContext context) {
+    if (!context.IsInString && token.TokenType == TokenType.String) {
+      context.IsInString = true;
+      context.OpenedStrings++;
+    } else if (context.IsInString && token.TokenType == TokenType.String) {
+      context.IsInString = false;
+      context.ClosedStrings++;
+    }
+  }
+}
diff --git a/EpplusFormulaParser/LexicalAnalysis/Token.cs b/EpplusFormulaParser/LexicalAnalysis/Token.cs
new file mode 100644
index 0000000..397cdd5
--- /dev/null
+++ b/EpplusFormulaParser/LexicalAnalysis/Token.cs
@@ -0,0 +1,50 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public class Token(string token, TokenType tokenType) {
+  public string Value { get; } = token;
+
+  public TokenType TokenType { get; internal set; } = tokenType;
+
+  public bool IsNegated { get; private set; }
+
+  public void Negate() {
+    if (TokenType is TokenType.Decimal or TokenType.Integer or TokenType.ExcelAddress) {
+      IsNegated = true;
+    }
+  }
+
+  public override string ToString() {
+    return TokenType + ", " + Value;
+  }
+}
diff --git a/EpplusFormulaParser/LexicalAnalysis/TokenFactory.cs b/EpplusFormulaParser/LexicalAnalysis/TokenFactory.cs
new file mode 100644
index 0000000..9084591
--- /dev/null
+++ b/EpplusFormulaParser/LexicalAnalysis/TokenFactory.cs
@@ -0,0 +1,115 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ * Jan Källman                      Replaced Adress validate    2013-03-01
+ * *******************************************************************************/
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace EpplusFormulaParser;
+
+internal static partial class TokenFactory {
+  public static Token Create(List<Token> tokens, string token) {
+    if (TokenSeparatorProvider.Tokens.TryGetValue(token, out var tokenSeparator)) {
+      return tokenSeparator;
+    }
+    var tokenList = (IList<Token>)tokens;
+    //Address with worksheet-string before  /JK
+    if (token.StartsWith('!') && tokenList[^1].TokenType == TokenType.String) {
+      var i = tokenList.Count - 2;
+      if (i > 0) {
+        string addr;
+        if (tokenList[i].TokenType == TokenType.StringContent) {
+          addr = "'" + tokenList[i].Value.Replace("'", "''") + "'";
+        } else {
+          throw new ArgumentException($"Invalid formula token sequence near {token}");
+        }
+        //Remove the string tokens and content
+        tokenList.RemoveAt(tokenList.Count - 1);
+        tokenList.RemoveAt(tokenList.Count - 1);
+        tokenList.RemoveAt(tokenList.Count - 1);
+
+        return new(addr + token, TokenType.ExcelAddress);
+      }
+      throw new ArgumentException($"Invalid formula token sequence near {token}");
+    }
+
+    if (tokens.Count != 0 && tokens.Last().TokenType == TokenType.String) {
+      return new(token, TokenType.StringContent);
+    }
+    if (!string.IsNullOrEmpty(token)) {
+      token = token.Trim();
+    }
+    if (DecimalRegex().IsMatch(token)) {
+      return new(token, TokenType.Decimal);
+    }
+    if (IntegerRegex().IsMatch(token)) {
+      return new(token, TokenType.Integer);
+    }
+    if (BooleanRegex().IsMatch(token)) {
+      return new(token, TokenType.Boolean);
+    }
+    if (token.Contains("#REF!", StringComparison.InvariantCultureIgnoreCase)) {
+      return new(token, TokenType.InvalidReference);
+    }
+    if (token.Equals("#NUM!", StringComparison.InvariantCultureIgnoreCase)) {
+      return new(token, TokenType.NumericError);
+    }
+    if (token.Equals("#VALUE!", StringComparison.InvariantCultureIgnoreCase)) {
+      return new(token, TokenType.ValueDataTypeError);
+    }
+    if (token.Equals("#NULL!", StringComparison.InvariantCultureIgnoreCase)) {
+      return new(token, TokenType.Null);
+    }
+    if (FunctionRepository.IsFunctionName(token)) {
+      return new(token, TokenType.Function);
+    }
+    if (tokenList.Count > 0 && tokenList[^1].TokenType == TokenType.OpeningEnumerable) {
+      return new(token, TokenType.Enumerable);
+    }
+    var at = ExcelAddressUtilities.IsValid(token);
+    if (at == ExcelAddressUtilities.AddressType.InternalAddress) {
+      return new(token.ToUpper(CultureInfo.InvariantCulture), TokenType.ExcelAddress);
+    }
+    return new(token, TokenType.Unrecognized);
+  }
+
+  [GeneratedRegex(@"^[0-9]+\.[0-9]+$")]
+  private static partial Regex DecimalRegex();
+
+  [GeneratedRegex("^[0-9]+$")]
+  private static partial Regex IntegerRegex();
+
+  [GeneratedRegex("^(true|false)$", RegexOptions.IgnoreCase)]
+  private static partial Regex BooleanRegex();
+}
diff --git a/EpplusFormulaParser/LexicalAnalysis/TokenSeparatorProvider.cs b/EpplusFormulaParser/LexicalAnalysis/TokenSeparatorProvider.cs
new file mode 100644
index 0000000..f505227
--- /dev/null
+++ b/EpplusFormulaParser/LexicalAnalysis/TokenSeparatorProvider.cs
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Collections.Frozen;
+using System.Collections.Generic;
+
+namespace EpplusFormulaParser;
+
+internal static class TokenSeparatorProvider {
+  private static readonly FrozenDictionary<string, Token> _tokens = new Dictionary<string, Token> {
+    { "+", new("+", TokenType.Operator) },
+    { "-", new("-", TokenType.Operator) },
+    { "*", new("*", TokenType.Operator) },
+    { "/", new("/", TokenType.Operator) },
+    { "^", new("^", TokenType.Operator) },
+    { "&", new("&", TokenType.Operator) },
+    { ">", new(">", TokenType.Operator) },
+    { "<", new("<", TokenType.Operator) },
+    { "=", new("=", TokenType.Operator) },
+    { "<=", new("<=", TokenType.Operator) },
+    { ">=", new(">=", TokenType.Operator) },
+    { "<>", new("<>", TokenType.Operator) },
+    { "(", new("(", TokenType.OpeningParenthesis) },
+    { ")", new(")", TokenType.ClosingParenthesis) },
+    { "{", new("{", TokenType.OpeningEnumerable) },
+    { "}", new("}", TokenType.ClosingEnumerable) },
+    { "'", new("'", TokenType.String) },
+    { "\"", new("\"", TokenType.String) },
+    { ",", new(",", TokenType.Comma) },
+    { ";", new(";", TokenType.SemiColon) },
+    { "[", new("[", TokenType.OpeningBracket) },
+    { "]", new("]", TokenType.ClosingBracket) },
+    { "%", new("%", TokenType.Percent) },
+  }.ToFrozenDictionary();
+
+  public static IDictionary<string, Token> Tokens => _tokens;
+
+  public static bool IsOperator(string item) =>
+    _tokens.TryGetValue(item, out var token) && token.TokenType == TokenType.Operator;
+
+  public static bool IsPossibleLastPartOfMultipleCharOperator(string part) => part is "=" or ">";
+}
diff --git a/EpplusFormulaParser/LexicalAnalysis/TokenType.cs b/EpplusFormulaParser/LexicalAnalysis/TokenType.cs
new file mode 100644
index 0000000..0c689c0
--- /dev/null
+++ b/EpplusFormulaParser/LexicalAnalysis/TokenType.cs
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+namespace EpplusFormulaParser;
+
+public enum TokenType {
+  Operator,
+  Negator,
+  OpeningParenthesis,
+  ClosingParenthesis,
+  OpeningEnumerable,
+  ClosingEnumerable,
+  OpeningBracket,
+  ClosingBracket,
+  Enumerable,
+  Comma,
+  SemiColon,
+  String,
+  StringContent,
+  Integer,
+  Boolean,
+  Decimal,
+  Percent,
+  Function,
+  ExcelAddress,
+  NameValue,
+  InvalidReference,
+  NumericError,
+  ValueDataTypeError,
+  Null,
+  Unrecognized,
+}
diff --git a/EpplusFormulaParser/LexicalAnalysis/TokenizerContext.cs b/EpplusFormulaParser/LexicalAnalysis/TokenizerContext.cs
new file mode 100644
index 0000000..76b839a
--- /dev/null
+++ b/EpplusFormulaParser/LexicalAnalysis/TokenizerContext.cs
@@ -0,0 +1,79 @@
+/*******************************************************************************
+ * You may amend and distribute as you like, but don't remove this header!
+ *
+ * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets.
+ * See http://www.codeplex.com/EPPlus for details.
+ *
+ * Copyright (C) 2011  Jan Källman
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU Lesser General Public License for more details.
+ *
+ * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php
+ * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html
+ *
+ * All code and executables are provided "as is" with no warranty either express or implied.
+ * The author accepts no liability for any damage or loss of business that this product may cause.
+ *
+ * Code change notes:
+ *
+ * Author							Change						Date
+ * ******************************************************************************
+ * Mats Alm   		                Added       		        2013-03-01 (Prior file history on https://github.com/swmal/ExcelFormulaParser)
+ *******************************************************************************/
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace EpplusFormulaParser;
+
+internal sealed class TokenizerContext(string formula) {
+  private StringBuilder _currentToken = new();
+
+  public string Formula { get; } = formula;
+
+  public List<Token> Result { get; } = [];
+
+  public bool IsInString { get; private set; }
+
+  public void ToggleIsInString() {
+    IsInString = !IsInString;
+  }
+
+  internal int BracketCount { get; set; }
+
+  public string CurrentToken => _currentToken.ToString();
+
+  public bool CurrentTokenHasValue =>
+    !string.IsNullOrEmpty(IsInString ? CurrentToken : CurrentToken.Trim());
+
+  public void NewToken() {
+    _currentToken = new();
+  }
+
+  public void AddToken(Token token) {
+    Result.Add(token);
+  }
+
+  public void AppendToCurrentToken(char c) {
+    _currentToken.Append(c.ToString());
+  }
+
+  public void ReplaceLastToken(Token newToken) {
+    var count = Result.Count;
+    if (count > 0) {
+      Result.RemoveAt(count - 1);
+    }
+    Result.Add(newToken);
+  }
+
+  public Token? LastToken => Result.Count > 0 ? Result.Last() : null;
+}