diff --git a/MathEngine/EngineTests/EngineTests.csproj b/MathEngine/EngineTests/EngineTests.csproj
new file mode 100644
index 0000000..5217795
--- /dev/null
+++ b/MathEngine/EngineTests/EngineTests.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MathEngine/EngineTests/Parser Tests/ExpressionTreeTests.cs b/MathEngine/EngineTests/Parser Tests/ExpressionTreeTests.cs
new file mode 100644
index 0000000..08a1862
--- /dev/null
+++ b/MathEngine/EngineTests/Parser Tests/ExpressionTreeTests.cs
@@ -0,0 +1,59 @@
+using MathEngine.Parser.Tokeniser;
+using MathEngine.Parser.Parser;
+namespace EngineTests
+{
+ ///
+ /// Class for testing the ExpressionTree Class
+ ///
+ [TestClass]
+ public class ExpressionTreeTests
+ {
+
+ ///
+ /// Test to see if a simple expression is constructed correctly
+ ///
+ [TestMethod]
+ public void TestExpressionTreeSimpleExpression()
+ {
+ string testExp = "3+4";
+ TreeNode exptectedTree = new(Token.Plus);
+ Token tokfour = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Token tokthree = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ TreeNode four = new(tokfour);
+ TreeNode three = new(tokthree);
+ exptectedTree.AddChildNode(four);
+ exptectedTree.AddChildNode(three);
+ ExpressionTree returnedTree = new ExpressionTree(testExp);
+ Assert.IsTrue(returnedTree.Equals(exptectedTree));
+ }
+
+ ///
+ /// Test to see if a simple expression is evaluated correctly
+ ///
+ [TestMethod]
+ public void TestExpressionTreeSimpleExpressionEvaluation()
+ {
+ string testExp = "3+4*7";
+ Token tok31 = new("31", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ TreeNode exptectedTree = new(tok31);
+ ExpressionTree returnedTree = new ExpressionTree(testExp);
+ ExpressionTree evaluatedTree = returnedTree.Evaluate();
+ Assert.IsTrue(evaluatedTree.Equals(exptectedTree));
+ }
+
+ ///
+ /// Test to see if a simple expression using all base operators (+,-,*,/) is evaluated correctly
+ ///
+ [TestMethod]
+ public void TestExpressionTreeSimpleExpressionAllBaseOperatorsEvaluation()
+ {
+ string testExp = "3+4*7-8/7";
+ decimal testValue = decimal.Divide(209 , 7);
+ Token tok31 = new(testValue.ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ TreeNode exptectedTree = new(tok31);
+ ExpressionTree returnedTree = new ExpressionTree(testExp);
+ ExpressionTree evaluatedTree = returnedTree.Evaluate();
+ Assert.IsTrue(evaluatedTree.Equals(exptectedTree));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MathEngine/EngineTests/Parser Tests/ParserTests.cs b/MathEngine/EngineTests/Parser Tests/ParserTests.cs
new file mode 100644
index 0000000..4975f79
--- /dev/null
+++ b/MathEngine/EngineTests/Parser Tests/ParserTests.cs
@@ -0,0 +1,92 @@
+using MathEngine.Parser.Tokeniser;
+using MathEngine.Parser.Parser;
+namespace EngineTests
+{
+ ///
+ /// Class for testing the Parser
+ ///
+ [TestClass]
+ public class ParserTests
+ {
+ ///
+ /// Test the Parser on a basic List of tokens
+ ///
+ [TestMethod]
+ public void TestParserBasicExpression()
+ {
+ //Arrange
+ string testString = "3+4";
+ List testList = Tokeniser.Tokenise(testString);
+ Token three = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Token four = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Assert.IsNotNull(testList);
+ Stack expectedStack = new();
+ expectedStack.Push(Token.Plus);
+ expectedStack.Push(four);
+ expectedStack.Push(three);
+ //Act
+ Stack returnedStack = Parser.Parse(testList);
+ //Assert
+ if (returnedStack.Count != expectedStack.Count)
+ {
+ Assert.Fail();
+ }
+ else
+ {
+ while (returnedStack.Count > 0)
+ {
+ if (!returnedStack.Pop().Equals(expectedStack.Pop()))
+ {
+ Assert.Fail();
+ }
+ }
+ }
+ }
+
+ ///
+ /// Test the Parser on a more compilicated basic expression to see if operator precedence is respected
+ ///
+ [TestMethod]
+ public void TestParserBasicExpressionAllOperators()
+ {
+ //Arrange
+ string testString = "3+4*8-47.2/9";
+ List testList = Tokeniser.Tokenise(testString);
+ Token three = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Token four = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Token eight = new("8", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Token nine = new("9", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Token fourSevenPoint2 = new("47.2", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Assert.IsNotNull(testList);
+ Stack expectedStack = new();
+ expectedStack.Push(Token.Minus);
+ expectedStack.Push(Token.Divide);
+ expectedStack.Push(nine);
+ expectedStack.Push(fourSevenPoint2);
+ expectedStack.Push(Token.Plus);
+ expectedStack.Push(Token.Multiply);
+ expectedStack.Push(eight);
+ expectedStack.Push(four);
+ expectedStack.Push(three);
+ //Act
+ Stack returnedStack = Parser.Parse(testList);
+ //Assert
+ if (returnedStack.Count != expectedStack.Count)
+ {
+ Assert.Fail();
+ }
+ else
+ {
+ while (returnedStack.Count > 0)
+ {
+ if (!returnedStack.Pop().Equals(expectedStack.Pop()))
+ {
+ Assert.Fail();
+ }
+ }
+ }
+ }
+
+
+ }
+}
diff --git a/MathEngine/EngineTests/Parser Tests/TokenIserTests.cs b/MathEngine/EngineTests/Parser Tests/TokenIserTests.cs
new file mode 100644
index 0000000..e0c2911
--- /dev/null
+++ b/MathEngine/EngineTests/Parser Tests/TokenIserTests.cs
@@ -0,0 +1,65 @@
+using MathEngine.Parser.Tokeniser;
+
+namespace EngineTests
+{
+ ///
+ /// Class for testing the Tokeniser
+ ///
+ [TestClass]
+ public class TokeniserTests
+ {
+ ///
+ /// Test the tokeniser on a basic string
+ ///
+ [TestMethod]
+ public void TestTokeniseBasicString()
+ {
+ //Arrange
+ string testString = "1+1";
+ Token one = new("1", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ List expectedValue = new()
+ {
+ one,
+ Token.Plus,
+ one
+ };
+ //Act
+ List returnedValue = Tokeniser.Tokenise(testString);
+ //Assert
+ Assert.IsTrue(expectedValue.SequenceEqual(returnedValue));
+ }
+
+ ///
+ /// Test the tokeniser on a basic string, but with significant ammounts of whitespace
+ ///
+ [TestMethod]
+ public void TestTokeniseBasicStringWithWhiteSpace()
+ {
+ //Arrange
+ string testString = " 1 + 1 ";
+ Token one = new("1", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ List expectedValue = new()
+ {
+ one,
+ Token.Plus,
+ one
+ };
+ //Act
+ List returnedValue = Tokeniser.Tokenise(testString);
+ //Assert
+ Assert.IsTrue(expectedValue.SequenceEqual(returnedValue));
+ }
+
+ ///
+ /// Test the tokeniser on a string which contains a number which is not formatted correctly
+ ///
+ [TestMethod]
+ public void TestTokeniseStringWithInvalidNumbr()
+ {
+ //Arrange
+ string testString = "1+11.2.5";
+ //Act and Assert
+ Assert.ThrowsException(() => Tokeniser.Tokenise(testString));
+ }
+ }
+}
\ No newline at end of file
diff --git a/MathEngine/EngineTests/Parser Tests/TreeNodeTests.cs b/MathEngine/EngineTests/Parser Tests/TreeNodeTests.cs
new file mode 100644
index 0000000..7bca15a
--- /dev/null
+++ b/MathEngine/EngineTests/Parser Tests/TreeNodeTests.cs
@@ -0,0 +1,30 @@
+using MathEngine.Parser.Tokeniser;
+using MathEngine.Parser.Parser;
+namespace EngineTests
+{
+ ///
+ /// Class for testing the TreeNode class
+ ///
+ [TestClass]
+ public class TreeNodeTests
+ {
+
+ ///
+ /// Test to see if a simple expression is constructed correctly
+ ///
+ [TestMethod]
+ public void TestTreeNodexpression()
+ {
+ string testExp = "3+4";
+ TreeNode exptectedTree = new(Token.Plus);
+ Token tokfour = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ Token tokthree = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
+ TreeNode four = new(tokfour);
+ TreeNode three = new(tokthree);
+ exptectedTree.AddChildNode(four);
+ exptectedTree.AddChildNode(three);
+ ExpressionTree returnedTree = new (testExp);
+ Assert.IsTrue(exptectedTree.Equals(returnedTree));
+ }
+ }
+}
diff --git a/MathEngine/EngineTests/Usings.cs b/MathEngine/EngineTests/Usings.cs
new file mode 100644
index 0000000..ab67c7e
--- /dev/null
+++ b/MathEngine/EngineTests/Usings.cs
@@ -0,0 +1 @@
+global using Microsoft.VisualStudio.TestTools.UnitTesting;
\ No newline at end of file
diff --git a/MathEngine/MathEngine.sln b/MathEngine/MathEngine.sln
new file mode 100644
index 0000000..3c30a4b
--- /dev/null
+++ b/MathEngine/MathEngine.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32616.157
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathEngine", "MathEngine\MathEngine.csproj", "{E4A483AB-44FC-4386-A509-C612FE6E6C8A}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineTests", "EngineTests\EngineTests.csproj", "{096BD3DE-E398-42AD-875F-6BEA469ED78F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {E4A483AB-44FC-4386-A509-C612FE6E6C8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E4A483AB-44FC-4386-A509-C612FE6E6C8A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E4A483AB-44FC-4386-A509-C612FE6E6C8A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E4A483AB-44FC-4386-A509-C612FE6E6C8A}.Release|Any CPU.Build.0 = Release|Any CPU
+ {096BD3DE-E398-42AD-875F-6BEA469ED78F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {096BD3DE-E398-42AD-875F-6BEA469ED78F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {096BD3DE-E398-42AD-875F-6BEA469ED78F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {096BD3DE-E398-42AD-875F-6BEA469ED78F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {DF90889C-36A5-4730-82C6-91E0B39FDF91}
+ EndGlobalSection
+EndGlobal
diff --git a/MathEngine/MathEngine/MathEngine.csproj b/MathEngine/MathEngine/MathEngine.csproj
new file mode 100644
index 0000000..b9af703
--- /dev/null
+++ b/MathEngine/MathEngine/MathEngine.csproj
@@ -0,0 +1,14 @@
+
+
+
+ net6.0
+ enable
+ enable
+
+
+
+ <_Parameter1>EngineTests
+
+
+
+
diff --git a/MathEngine/MathEngine/Parser/Parser/ExpressionTree.cs b/MathEngine/MathEngine/Parser/Parser/ExpressionTree.cs
new file mode 100644
index 0000000..9c09c54
--- /dev/null
+++ b/MathEngine/MathEngine/Parser/Parser/ExpressionTree.cs
@@ -0,0 +1,211 @@
+using System.Collections.Generic;
+using MathEngine.Parser.Tokeniser;
+namespace MathEngine.Parser.Parser
+{
+ ///
+ /// Represents an Abstract Syntax tree for expresison evaluation
+ ///
+ internal class ExpressionTree
+ {
+ ///
+ /// The root node of the expression tree;
+ ///
+ private readonly TreeNode? rootNode;
+
+ ///
+ /// Initialises a new instance of the MathEngine.Parser.Parser.Node class with a given Token
+ /// The token for the nodes value
+ ///
+ ///
+ public ExpressionTree(string Expression)
+ {
+ List tokens = Tokeniser.Tokeniser.Tokenise(Expression);
+ Stack rpnForm = Parser.Parse(tokens);
+ rootNode = GenerateExpressionTree(rpnForm);
+ }
+
+ private ExpressionTree(TreeNode rootNode)
+ {
+ this.rootNode = rootNode;
+ }
+
+ ///
+ /// Creates a binary TreeNode, that is a node with a root value and two children
+ ///
+ /// The token to be the root node of the TreeNode
+ /// TreeNode that is the left branch of the current node
+ /// TreeNode that is the right branch of the current node
+ /// A TreeNode with CurrentToken as the root value and LeftBranch and RightBranch as Children
+ private static TreeNode CreateBinaryNode(Token CurrentToken, TreeNode LeftBranch, TreeNode RightBranch)
+ {
+ TreeNode root = new(CurrentToken);
+ root.AddChildNode(LeftBranch);
+ root.AddChildNode(RightBranch);
+ return root;
+ }
+
+ ///
+ /// Creates a unary TreeNode, that is a node with a root value and two children
+ ///
+ /// The token to be the root node of the TreeNode
+ /// TreeNode that is the child of the current node
+ /// A TreeNode with CurrentToken as the root value and ChildNode as the sole child node
+ private static TreeNode CreateUnaryNode(Token CurrentToken, TreeNode ChildNode)
+ {
+ TreeNode root = new(CurrentToken);
+ root.AddChildNode(ChildNode);
+ return root;
+ }
+
+ ///
+ /// Generates the full expression tree given an RPN expression stack
+ ///
+ /// RPN expression stack to generate an expression tree from
+ /// An expression Tree that represents the Mathematical expression given by rpnExpression
+ private static TreeNode? GenerateExpressionTree(Stack rpnExpression)
+ {
+ Stack OutputStack = new(rpnExpression.Count);
+ TreeNode Node;
+ Token CurrentToken;
+ if (rpnExpression.Count == 0)
+ {
+ return null;
+ }
+ else
+ {
+ while (rpnExpression.Count != 0)
+ {
+ CurrentToken = rpnExpression.Pop();
+ switch (CurrentToken.Token_Type)
+ {
+ case Token.Type.Numeric:
+ Node = new TreeNode(CurrentToken);
+ OutputStack.Push(Node);
+ break;
+ case Token.Type.Addition: // We need to preserve "Left handness" i.e 7/8 gives a root node of / with Cnode(0) = 7 and Cnod(1) = 8 etc. This should preserve non commutativity
+ case Token.Type.Subtraction:
+ case Token.Type.Multiplication:
+ case Token.Type.Division:
+ case Token.Type.Exponentiation:
+ TreeNode Right = OutputStack.Pop();
+ TreeNode Left = OutputStack.Pop();
+ Node = CreateBinaryNode(CurrentToken, Left, Right);
+ OutputStack.Push(Node);
+ break;
+ case Token.Type.UnaryPlus:
+ case Token.Type.UnaryMinus:
+ Node = CreateUnaryNode(CurrentToken, OutputStack.Pop());
+ OutputStack.Push(Node);
+ break;
+ }
+ }
+ return OutputStack.Pop();
+ }
+ }
+
+
+ ///
+ /// Evaluates branches of a given tree
+ ///
+ ///
+ ///
+ private static TreeNode Evaluate_Tree_Branch(TreeNode Branch)
+ {
+ TreeNode Root, LeftBranch, RightBranch;
+ if (Branch.NodeValue.Token_Type == Token.Type.Numeric)
+ return Branch;
+ else
+ {
+ LeftBranch = Evaluate_Tree_Branch(Branch.GetChildNode(0));
+ RightBranch = Evaluate_Tree_Branch(Branch.GetChildNode(1));
+ // We finally combine the computed branches with the operator that links them and return the result
+ Root = Evaluate_Operator(Branch.NodeValue, LeftBranch, RightBranch);
+ return Root;
+ }
+ }
+
+ ///
+ /// Evlautes a binary node where the root node is an operator and given two branches the left and the right
+ ///
+ ///
+ ///
+ ///
+ ///
+ private static TreeNode Evaluate_Operator(Token Operator_Token, TreeNode Left_Branch, TreeNode Right_Branch)
+ {
+ decimal lhs = decimal.Parse(Left_Branch.NodeValue.TokenValue);
+ decimal rhs = decimal.Parse(Right_Branch.NodeValue.TokenValue);
+ return Operator_Token.Token_Type switch
+ {
+ Token.Type.Addition => new TreeNode(new Token((lhs + rhs).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
+ Token.Type.Subtraction => new TreeNode(new Token((lhs - rhs).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
+ Token.Type.Multiplication => new TreeNode(new Token(((decimal)(lhs * rhs)).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
+ Token.Type.Division => new TreeNode(new Token(((decimal)(lhs / rhs)).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
+ _ => throw new Exception("Potentially invalid token?"),
+ };
+ }
+
+ ///
+ /// Evaluates the current instance of ExpressionTree
+ ///
+ /// Returns an update of the current instance which the expression Evaluated
+ public ExpressionTree? Evaluate()
+ {
+ if (rootNode == null)
+ {
+ return null;
+ }
+ else // To evaluate we go anti-clockwise around the tree
+ {
+ TreeNode? Root, LeftBranch, RightBranch;
+ if (rootNode.NodeValue.Token_Type == Token.Type.Numeric)
+ return this;
+ else
+ {
+ LeftBranch = Evaluate_Tree_Branch(rootNode.GetChildNode(0));
+ RightBranch = Evaluate_Tree_Branch(rootNode.GetChildNode(1));
+ Root = Evaluate_Operator(rootNode.NodeValue, LeftBranch, RightBranch);
+ return new ExpressionTree(Root);
+ }
+ }
+ }
+
+ ///
+ /// Returns a value indicating if the given object is equal to the current instance of ExpressionTree
+ ///
+ /// The object to compare to the current instance
+ ///
+ public override bool Equals(object? other)
+ {
+ if (other is TreeNode)
+ {
+ ExpressionTree otherTree = new((TreeNode)other);
+ return this.Equals(otherTree);
+ }
+ return false;
+ }
+
+ ///
+ /// Compares the current ExpressionTree instance for equality with the given ExpressionTree
+ ///
+ /// The ExpressionTree to compare to the current instance
+ /// True if the expression trees are equal and False otherwise
+ public bool Equals(ExpressionTree other)
+ {
+ if (this == null || other == null)
+ {
+ return false;
+ }
+ if (this.rootNode == null || other.rootNode == null)
+ {
+ return false;
+ }
+ return this.rootNode.Equals(other.rootNode);
+ }
+
+ public override int GetHashCode()
+ {
+ return System.HashCode.Combine(this.rootNode);
+ }
+ }
+}
diff --git a/MathEngine/MathEngine/Parser/Parser/Node/TreeNode.cs b/MathEngine/MathEngine/Parser/Parser/Node/TreeNode.cs
new file mode 100644
index 0000000..bc78b6d
--- /dev/null
+++ b/MathEngine/MathEngine/Parser/Parser/Node/TreeNode.cs
@@ -0,0 +1,153 @@
+using MathEngine.Parser.Tokeniser;
+namespace MathEngine.Parser.Parser
+{
+ ///
+ /// Represents a node in a Tree structure
+ ///
+ internal class TreeNode
+ {
+ private TreeNode? Parent;
+ private List? Children;
+ private readonly Token Value;
+
+ ///
+ /// Initialises a new instance of the MathEngine.Parser.Parser.Node class with a given Token
+ ///
+ /// The token for the nodes value
+ public TreeNode(Token value)
+ {
+ Parent = null;
+ Children = null;
+ Value = value;
+ }
+
+ ///
+ /// Returns the value of the node
+ ///
+ public Token NodeValue
+ {
+ get { return Value; }
+ }
+
+ ///
+ /// Returns the parent node of the current node, or null if it does not exist
+ ///
+ public TreeNode? ParentNode
+ {
+ get
+ {
+ if (Parent == null)
+ {
+ return null;
+ }
+ else
+ {
+ return Parent;
+ }
+ }
+ }
+
+ ///
+ /// Returns all of the child nodes of the current node, or null if it odes not exist
+ ///
+ public List? GetChildrenNodes
+ {
+ get
+ {
+ if (Children == null)
+ {
+ return null;
+ }
+ else
+ {
+ return Children;
+ }
+ }
+ }
+
+ ///
+ /// Returns the child node specified by the index, if there are no children nodes or if the index is out of bounds than null is returned
+ ///
+ ///
+ ///
+ public TreeNode? GetChildNode(int index)
+ {
+ if (Children == null)
+ {
+ return null;
+ }
+ if (index < 0 || index >= Children.Count)
+ {
+ return null;
+ }
+
+ return Children[index];
+ }
+
+ ///
+ /// Adds a child node to the current root node, if there are no children nodes a list is created
+ ///
+ /// The value for the child node that is to be added
+ public void AddChildNode(TreeNode Node)
+ {
+ if (Children == null)
+ {
+ Children = new()
+ {
+ Node
+ };
+ }
+ else
+ {
+ Children.Add(Node);
+ }
+ }
+
+ ///
+ /// Returns a value that indicates if the given object is equal to the current instance of TreeNode
+ ///
+ /// The object to compare to the current instance of TreeNode
+ ///
+ public override bool Equals(object? other)
+ {
+ if (other is ExpressionTree)
+ {
+ return other.Equals(this);
+ }
+ return false;
+ }
+
+ public bool Equals(TreeNode other)
+ {
+ if (this.Value != other.Value) //If the root values are not equal we are done
+ {
+ return false;
+ }
+ // otherwise,
+ if (this.Children != null && other.Children != null) // If both children are NOT null then we reursively check the child nodes
+ {
+ //Covered all nullable cases, we now need to recursively check the child nodes
+ if (Children.Count != other.Children.Count)
+ {
+ return false;
+ }
+ for (int childNodeIndex = 0; childNodeIndex < Children.Count; childNodeIndex++)
+ {
+ if (!Children[childNodeIndex].Equals(other.Children[childNodeIndex]))
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else if (this.Children == null && other.Children == null) //Special case is if both children lists are null then the TreeNodes are equal
+ {
+ return true;
+ }
+ else // otherwise at least one is null and the other is not so they can't be equal
+ {
+ return false; // if both children are not null than at least one is null so they can't be equal
+ }
+ }
+ }
+}
diff --git a/MathEngine/MathEngine/Parser/Parser/Parser.cs b/MathEngine/MathEngine/Parser/Parser/Parser.cs
new file mode 100644
index 0000000..f4b7a26
--- /dev/null
+++ b/MathEngine/MathEngine/Parser/Parser/Parser.cs
@@ -0,0 +1,142 @@
+using MathEngine.Parser.Tokeniser;
+namespace MathEngine.Parser.Parser
+{
+ ///
+ /// Represents the conversion from a list of Tokens representing Mathematical expression to List in Reverse Polish Notation form
+ ///
+ internal class Parser
+ {
+
+ ///
+ /// Return the Precdence of a given token operator
+ ///
+ /// Token to get Precdence of
+ ///
+ private static int OperatorPrecedence(Token X)
+ {
+ switch (X.Token_Type)
+ {
+ case Token.Type.Addition:
+ case Token.Type.Subtraction:
+ {
+ return 1;
+ }
+ case Token.Type.Multiplication:
+ case Token.Type.Division:
+ {
+ return 2;
+ }
+ case Token.Type.UnaryPlus:
+ case Token.Type.UnaryMinus:
+ {
+ return 3;
+ }
+ case Token.Type.Exponentiation:
+ {
+ return 4;
+ }
+ case Token.Type.LeftParenthesis:
+ case Token.Type.RightParenthesis:
+ {
+ return 5;
+ }
+
+ default:
+ {
+ throw new Exception("Unknown operator precedence" + X.TokenValue);
+ }
+ }
+ }
+
+ ///
+ /// Is the operation left associative
+ ///
+ /// Operation to check
+ ///
+ private static bool IsLeftAssociatve(Token X)
+ {
+ switch (X.Token_Type)
+ {
+ case Token.Type.Addition:
+ case Token.Type.Subtraction:
+ case Token.Type.Multiplication:
+ case Token.Type.Division:
+ case Token.Type.LeftParenthesis:
+ case Token.Type.RightParenthesis:
+ {
+ return true;
+ }
+ case Token.Type.Exponentiation:
+ case Token.Type.UnaryMinus:
+ {
+ return false;
+ }
+ default:
+ {
+ throw new Exception("If you are seeing this something went wrong when trying to determine if a token was Left Associatve");
+
+ }
+ }
+ }
+
+ ///
+ /// ''' Reverse the order of a given stack of Tokens
+ /// '''
+ /// ''' Stack to reverse
+ /// '''
+ private static Stack ReverseStackTok(Stack Stack)
+ {
+ Stack OutputStack = new (Stack.Count);
+ while ((Stack.Count != 0))
+ OutputStack.Push(Stack.Pop());
+ return OutputStack;
+ }
+
+ ///
+ /// Parses a list of tokens into a valid RPN expression stack
+ ///
+ /// List of tokens to parse
+ /// Returns the Reverse polish notation form of the expression
+ static internal Stack Parse(List Expression)
+ {
+ //Temp holding stack for operators
+ Stack OperatorStack = new(Expression.Count/2);
+ //The final stack to return
+ Stack OutputStack = new(Expression.Count);
+ //Stack used to hold the number of input params to a function
+ //Stack ArityStack = new Stack();
+ Token CurrentToken;
+
+ for (int i = 0; i < Expression.Count; i++)
+ {
+ CurrentToken = Expression[i];
+ switch (CurrentToken.Token_Type)
+ {
+ case Token.Type.Numeric:
+ OutputStack.Push(CurrentToken);
+ break;
+ case Token.Type.Addition:
+ case Token.Type.Subtraction:
+ case Token.Type.Multiplication:
+ case Token.Type.Division:
+ while ((OperatorStack.Count != 0 && ((((OperatorStack.Peek().Token_Type == Token.Type.Function) | (OperatorPrecedence(OperatorStack.Peek()) > OperatorPrecedence(CurrentToken)) | ((OperatorPrecedence(OperatorStack.Peek()) == OperatorPrecedence(CurrentToken)) & (IsLeftAssociatve(CurrentToken)))) && !(OperatorStack.Peek().Token_Type == Token.Type.LeftParenthesis)))))
+ {
+ OutputStack.Push(OperatorStack.Pop());
+ }
+ OperatorStack.Push(CurrentToken);
+ break;
+ }
+ }
+
+ while ((OperatorStack.Count > 0))
+ {
+ if (OperatorStack.Peek().Token_Type == Token.Type.LeftParenthesis || OperatorStack.Peek().Token_Type == Token.Type.RightParenthesis)
+ throw new Exception("Mismatched parentheses; Expected (");
+ else
+ OutputStack.Push(OperatorStack.Pop());
+ }
+
+ return ReverseStackTok(OutputStack);
+ }
+ }
+}
diff --git a/MathEngine/MathEngine/Parser/Tokeniser/Token.cs b/MathEngine/MathEngine/Parser/Tokeniser/Token.cs
new file mode 100644
index 0000000..52f3ce3
--- /dev/null
+++ b/MathEngine/MathEngine/Parser/Tokeniser/Token.cs
@@ -0,0 +1,244 @@
+namespace MathEngine.Parser.Tokeniser
+{
+ ///
+ /// Defines the Token Type. The base for all manipulations
+ ///
+ internal struct Token
+ {
+ ///
+ /// Represents the token for +
+ ///
+ public static readonly Token Plus = new("+",Type.Addition,NumericType.NaN,0);
+
+ ///
+ /// Represents the token for -
+ ///
+ public static readonly Token Minus = new("-", Type.Subtraction, NumericType.NaN, 0);
+
+ ///
+ /// Represents the token for *
+ ///
+ public static readonly Token Multiply = new("*", Type.Multiplication, NumericType.NaN, 0);
+
+ ///
+ /// Represents the token for /
+ ///
+ public static readonly Token Divide = new("/", Type.Division, NumericType.NaN, 0);
+
+ ///
+ /// Enum representing the token type
+ ///
+ internal enum Type
+ {
+ Numeric,
+ DecimalPoint,
+ Addition,
+ Subtraction,
+ Multiplication,
+ Division,
+ Exponentiation,
+ UnaryPlus,
+ UnaryMinus,
+ Operator,
+ Variable,
+ Function,
+ FunctionArgumentSeparator,
+ LeftParenthesis,
+ RightParenthesis,
+ OpenBracket,
+ CloseBracket,
+ OpenBrace,
+ CloseBrace
+ }
+
+ ///
+ /// Enum representing the numerical type of the token
+ ///
+ internal enum NumericType
+ {
+ Integer,
+ Decimal,
+ Complex,
+ NaN
+ }
+
+ ///
+ /// String representing the value of the token
+ ///
+ private readonly string Value;
+
+ ///
+ /// The type of token
+ ///
+ private readonly Type TokenType;
+
+ ///
+ /// The numeric type of the token
+ ///
+ private readonly NumericType Numeric_Type;
+
+ ///
+ /// The arity of the token
+ ///
+ private readonly uint Arity;
+
+ #region "Properties"
+
+ ///
+ /// Returns the value of the Token
+ ///
+ public string TokenValue
+ {
+ get { return Value; }
+ }
+
+ ///
+ /// Returns the type of the token
+ ///
+ public readonly Type Token_Type
+ {
+ get { return TokenType; }
+ }
+
+ ///
+ /// Returns the numerical type of the token
+ ///
+ public readonly NumericType NumericalType
+ {
+ get { return Numeric_Type; }
+ }
+
+ ///
+ /// Returns the arity of the token
+ ///
+ public uint FunctionArity
+ {
+ get { return Arity; }
+ }
+
+ #endregion
+
+ ///
+ /// Initializes a new instance of the Tokeniser.Token structure with a given TokenValue, TokenType, TokenNumericType and Arity
+ ///
+ /// String representing the value of the token
+ /// The type that the token instance represents
+ /// The numeric type of the token
+ /// The token Arity
+ public Token(string TokenValue, Type TokenType, NumericType TokenNumericType, uint FunctionArity = 0)
+ {
+ this.Value = TokenValue;
+ this.TokenType = TokenType;
+ this.Numeric_Type = TokenNumericType;
+ this.Arity = FunctionArity;
+ }
+
+#if DEBUG
+ ///
+ /// Debug String; Used to give a string representation of a token
+ ///
+ ///
+ public new string ToString()
+ {
+ return Value + "," + TokenType.ToString() + "," + Numeric_Type.ToString() + "," + Arity.ToString();
+ }
+#endif
+
+ ///
+ /// Returns a value that indicates whether a two Tokens are equal.
+ ///
+ /// First Token to compare
+ /// Second Token to compare
+ /// Returns true if the two Tokens are equal and false otherwise
+ public static bool operator ==(Token X, Token Y)
+ {
+ if (X.TokenValue != Y.TokenValue)
+ {
+ return false;
+ }
+ if (X.TokenType != Y.TokenType)
+ {
+ return false;
+ }
+ if (X.NumericalType != Y.NumericalType)
+ {
+ return false;
+ }
+ if (X.Arity != Y.Arity)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ ///
+ /// Returns a value that indicates whether a two Tokens are not equal.
+ ///
+ /// First Token to compare
+ /// Second Token to compare
+ /// Returns true if the two Tokens are not equal and false otherwise
+ public static bool operator !=(Token X, Token Y)
+ {
+ if (X.TokenValue == Y.TokenValue)
+ {
+ return false;
+ }
+ if (X.TokenType == Y.TokenType)
+ {
+ return false;
+ }
+ if (X.NumericalType == Y.NumericalType)
+ {
+ return false;
+ }
+ if (X.Arity == Y.Arity)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public bool Equals(Token other)
+ {
+ if (this.TokenValue != other.TokenValue)
+ {
+ return false;
+ }
+ if (this.TokenType != other.TokenType)
+ {
+ return false;
+ }
+ if (this.NumericalType != other.NumericalType)
+ {
+ return false;
+ }
+ if (this.Arity != other.Arity)
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public override bool Equals(object? obj)
+ {
+ if (obj is Token)
+ {
+ Token other = (Token)obj;
+ return this.Equals(other);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Calculates the HashCode for the current Token Instance
+ ///
+ ///
+ public override int GetHashCode()
+ {
+ return HashCode.Combine(Value, TokenType, Numeric_Type, Arity);
+ }
+ }
+}
\ No newline at end of file
diff --git a/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs b/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs
new file mode 100644
index 0000000..46de9f2
--- /dev/null
+++ b/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs
@@ -0,0 +1,110 @@
+namespace MathEngine.Parser.Tokeniser
+{
+ ///
+ /// Represents the conversion of a Mathematical expression in string form to a List of Tokens
+ ///
+ static internal class Tokeniser
+ {
+ private static readonly List Operators = new(new char[] { '+', '-', '*', '/', '^' });
+
+ ///
+ /// Gets the next non-whitespace char or returns the null terminator is at EOS (End of stream)
+ ///
+ static private char GetNextChar(string Expresison, ref Int32 currentIndex)
+ {
+ char curChar;
+ do
+ {
+ currentIndex++;
+ curChar = currentIndex >= Expresison.Length ? '\0' : Expresison[currentIndex];
+
+ } while (char.IsWhiteSpace(curChar));
+ return curChar;
+ }
+
+ ///
+ /// Returns the token that represents the current character
+ ///
+ /// The character to get the token of
+ /// A token representing the current character, otherwise an exception is thrown
+ static private Token GetOperatorToken(char curChar)
+ {
+ return curChar switch
+ {
+ '+' => Token.Plus,
+ '-' => Token.Minus,
+ '*' => Token.Multiply,
+ '/' => Token.Divide,
+ _ => throw new Exception(String.Format("Character {0} is not a defined operator", curChar)),
+ };
+ }
+
+ ///
+ /// Tokenises a given Mathematical expression given as a string to a list of tokens
+ ///
+ /// Expression to tokenise
+ /// A list of tokens representing the given expression, if the expression string is null or empty then an empty list is returned
+ static internal List Tokenise(string Expression)
+ {
+ if (string.IsNullOrEmpty(Expression))
+ {
+ return new List { };
+ }
+ else
+ {
+ //Cleanup whitespace
+ Expression = String.Concat(Expression.Where(c => !Char.IsWhiteSpace(c)));
+ Int32 currentIndex = -1;
+ //Example expression 1+1+Sin[x]
+ List Tokenstack = new(Expression.Length); //Nearly always is overallocated to the true number of tokens but avoids the need to kkeep reallocating for a growing stack
+ char curChar;
+ while (currentIndex < Expression.Length)
+ {
+ curChar = GetNextChar(Expression,ref currentIndex);
+
+ //Switch on special characters
+ if (Operators.Contains(curChar))
+ {
+ Tokenstack.Add(Tokeniser.GetOperatorToken(curChar));
+ continue; //Next loop interation
+ }
+ //Number, two cases two consider, case 1) number is something like 142.2; case 2 .5 which is clearly 0.5.
+ //Case 1
+ if (Char.IsDigit(curChar))
+ {
+ bool hasDecimalPlace = false;
+ Int32 tempIndex = currentIndex;
+ char tempChar;
+ do
+ {
+ tempChar = GetNextChar(Expression, ref tempIndex);
+ if (tempChar == '.' && !hasDecimalPlace)
+ {
+ hasDecimalPlace = true;
+ }
+ else if (tempChar == '.' && hasDecimalPlace)
+ {
+ while (Char.IsDigit(tempChar) || tempChar == '.')
+ {
+ tempChar = GetNextChar(Expression, ref tempIndex);
+ }
+ string errString = Expression[currentIndex..tempIndex];
+ throw new Exception(String.Format("Syntax error: {0} has multiple decimal point when at most one is allowed",errString));
+ }
+ } while (Char.IsDigit(tempChar) || tempChar == '.');
+ //Iterate until we hit the next special character or EOS
+
+ //Token is a number so add this to the stack
+ Tokenstack.Add(new Token(Expression[currentIndex..tempIndex], Token.Type.Numeric, Token.NumericType.Decimal, 0));
+ currentIndex = tempIndex-1; //Sets the index variable to just before the non numeric char
+ }
+ }
+
+ //return the stack after triming
+ Tokenstack.TrimExcess();
+
+ return Tokenstack;
+ }
+ }
+ }
+}