mirror of
https://github.com/0xJ1M/MathsEngine.git
synced 2026-06-05 02:20:07 +00:00
Inital Commit
This commit is contained in:
22
MathEngine/EngineTests/EngineTests.csproj
Normal file
22
MathEngine/EngineTests/EngineTests.csproj
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||||
|
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
|
||||||
|
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MathEngine\MathEngine.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
59
MathEngine/EngineTests/Parser Tests/ExpressionTreeTests.cs
Normal file
59
MathEngine/EngineTests/Parser Tests/ExpressionTreeTests.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using MathEngine.Parser.Tokeniser;
|
||||||
|
using MathEngine.Parser.Parser;
|
||||||
|
namespace EngineTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for testing the ExpressionTree Class
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public class ExpressionTreeTests
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test to see if a simple expression is constructed correctly
|
||||||
|
/// </summary>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test to see if a simple expression is evaluated correctly
|
||||||
|
/// </summary>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test to see if a simple expression using all base operators (+,-,*,/) is evaluated correctly
|
||||||
|
/// </summary>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
92
MathEngine/EngineTests/Parser Tests/ParserTests.cs
Normal file
92
MathEngine/EngineTests/Parser Tests/ParserTests.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using MathEngine.Parser.Tokeniser;
|
||||||
|
using MathEngine.Parser.Parser;
|
||||||
|
namespace EngineTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for testing the Parser
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public class ParserTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test the Parser on a basic List of tokens
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void TestParserBasicExpression()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
string testString = "3+4";
|
||||||
|
List<Token> 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<Token> expectedStack = new();
|
||||||
|
expectedStack.Push(Token.Plus);
|
||||||
|
expectedStack.Push(four);
|
||||||
|
expectedStack.Push(three);
|
||||||
|
//Act
|
||||||
|
Stack<Token> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test the Parser on a more compilicated basic expression to see if operator precedence is respected
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void TestParserBasicExpressionAllOperators()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
string testString = "3+4*8-47.2/9";
|
||||||
|
List<Token> 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<Token> 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<Token> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
65
MathEngine/EngineTests/Parser Tests/TokenIserTests.cs
Normal file
65
MathEngine/EngineTests/Parser Tests/TokenIserTests.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using MathEngine.Parser.Tokeniser;
|
||||||
|
|
||||||
|
namespace EngineTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for testing the Tokeniser
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public class TokeniserTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test the tokeniser on a basic string
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void TestTokeniseBasicString()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
string testString = "1+1";
|
||||||
|
Token one = new("1", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||||
|
List<Token> expectedValue = new()
|
||||||
|
{
|
||||||
|
one,
|
||||||
|
Token.Plus,
|
||||||
|
one
|
||||||
|
};
|
||||||
|
//Act
|
||||||
|
List<Token> returnedValue = Tokeniser.Tokenise(testString);
|
||||||
|
//Assert
|
||||||
|
Assert.IsTrue(expectedValue.SequenceEqual(returnedValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test the tokeniser on a basic string, but with significant ammounts of whitespace
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void TestTokeniseBasicStringWithWhiteSpace()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
string testString = " 1 + 1 ";
|
||||||
|
Token one = new("1", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||||
|
List<Token> expectedValue = new()
|
||||||
|
{
|
||||||
|
one,
|
||||||
|
Token.Plus,
|
||||||
|
one
|
||||||
|
};
|
||||||
|
//Act
|
||||||
|
List<Token> returnedValue = Tokeniser.Tokenise(testString);
|
||||||
|
//Assert
|
||||||
|
Assert.IsTrue(expectedValue.SequenceEqual(returnedValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test the tokeniser on a string which contains a number which is not formatted correctly
|
||||||
|
/// </summary>
|
||||||
|
[TestMethod]
|
||||||
|
public void TestTokeniseStringWithInvalidNumbr()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
string testString = "1+11.2.5";
|
||||||
|
//Act and Assert
|
||||||
|
Assert.ThrowsException<Exception>(() => Tokeniser.Tokenise(testString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
MathEngine/EngineTests/Parser Tests/TreeNodeTests.cs
Normal file
30
MathEngine/EngineTests/Parser Tests/TreeNodeTests.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using MathEngine.Parser.Tokeniser;
|
||||||
|
using MathEngine.Parser.Parser;
|
||||||
|
namespace EngineTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for testing the TreeNode class
|
||||||
|
/// </summary>
|
||||||
|
[TestClass]
|
||||||
|
public class TreeNodeTests
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test to see if a simple expression is constructed correctly
|
||||||
|
/// </summary>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
MathEngine/EngineTests/Usings.cs
Normal file
1
MathEngine/EngineTests/Usings.cs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
global using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||||
31
MathEngine/MathEngine.sln
Normal file
31
MathEngine/MathEngine.sln
Normal file
@@ -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
|
||||||
14
MathEngine/MathEngine/MathEngine.csproj
Normal file
14
MathEngine/MathEngine/MathEngine.csproj
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||||
|
<_Parameter1>EngineTests</_Parameter1>
|
||||||
|
</AssemblyAttribute>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
211
MathEngine/MathEngine/Parser/Parser/ExpressionTree.cs
Normal file
211
MathEngine/MathEngine/Parser/Parser/ExpressionTree.cs
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using MathEngine.Parser.Tokeniser;
|
||||||
|
namespace MathEngine.Parser.Parser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an Abstract Syntax tree for expresison evaluation
|
||||||
|
/// </summary>
|
||||||
|
internal class ExpressionTree
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The root node of the expression tree;
|
||||||
|
/// </summary>
|
||||||
|
private readonly TreeNode? rootNode;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises a new instance of the MathEngine.Parser.Parser.Node class with a given Token
|
||||||
|
/// <param name="value">The token for the nodes value</param>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Expression"></param>
|
||||||
|
public ExpressionTree(string Expression)
|
||||||
|
{
|
||||||
|
List<Token> tokens = Tokeniser.Tokeniser.Tokenise(Expression);
|
||||||
|
Stack<Token> rpnForm = Parser.Parse(tokens);
|
||||||
|
rootNode = GenerateExpressionTree(rpnForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExpressionTree(TreeNode rootNode)
|
||||||
|
{
|
||||||
|
this.rootNode = rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a binary TreeNode, that is a node with a root value and two children
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="CurrentToken">The token to be the root node of the TreeNode</param>
|
||||||
|
/// <param name="LeftToken">TreeNode that is the left branch of the current node</param>
|
||||||
|
/// <param name="RightToken">TreeNode that is the right branch of the current node</param>
|
||||||
|
/// <returns>A TreeNode with CurrentToken as the root value and LeftBranch and RightBranch as Children</returns>
|
||||||
|
private static TreeNode CreateBinaryNode(Token CurrentToken, TreeNode LeftBranch, TreeNode RightBranch)
|
||||||
|
{
|
||||||
|
TreeNode root = new(CurrentToken);
|
||||||
|
root.AddChildNode(LeftBranch);
|
||||||
|
root.AddChildNode(RightBranch);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a unary TreeNode, that is a node with a root value and two children
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="CurrentToken">The token to be the root node of the TreeNode</param>
|
||||||
|
/// <param name="LeftToken">TreeNode that is the child of the current node</param>
|
||||||
|
/// <returns>A TreeNode with CurrentToken as the root value and ChildNode as the sole child node</returns>
|
||||||
|
private static TreeNode CreateUnaryNode(Token CurrentToken, TreeNode ChildNode)
|
||||||
|
{
|
||||||
|
TreeNode root = new(CurrentToken);
|
||||||
|
root.AddChildNode(ChildNode);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates the full expression tree given an RPN expression stack
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rpnExpression">RPN expression stack to generate an expression tree from</param>
|
||||||
|
/// <returns>An expression Tree that represents the Mathematical expression given by rpnExpression</returns>
|
||||||
|
private static TreeNode? GenerateExpressionTree(Stack<Token> rpnExpression)
|
||||||
|
{
|
||||||
|
Stack<TreeNode> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates branches of a given tree
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Branch"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evlautes a binary node where the root node is an operator and given two branches the left and the right
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Operator_Token"></param>
|
||||||
|
/// <param name="Left_Branch"></param>
|
||||||
|
/// <param name="Right_Branch"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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?"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Evaluates the current instance of ExpressionTree
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Returns an update of the current instance which the expression Evaluated</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value indicating if the given object is equal to the current instance of ExpressionTree
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The object to compare to the current instance</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override bool Equals(object? other)
|
||||||
|
{
|
||||||
|
if (other is TreeNode)
|
||||||
|
{
|
||||||
|
ExpressionTree otherTree = new((TreeNode)other);
|
||||||
|
return this.Equals(otherTree);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Compares the current ExpressionTree instance for equality with the given ExpressionTree
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The ExpressionTree to compare to the current instance</param>
|
||||||
|
/// <returns>True if the expression trees are equal and False otherwise</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
153
MathEngine/MathEngine/Parser/Parser/Node/TreeNode.cs
Normal file
153
MathEngine/MathEngine/Parser/Parser/Node/TreeNode.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
using MathEngine.Parser.Tokeniser;
|
||||||
|
namespace MathEngine.Parser.Parser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a node in a Tree structure
|
||||||
|
/// </summary>
|
||||||
|
internal class TreeNode
|
||||||
|
{
|
||||||
|
private TreeNode? Parent;
|
||||||
|
private List<TreeNode>? Children;
|
||||||
|
private readonly Token Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises a new instance of the MathEngine.Parser.Parser.Node class with a given Token
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The token for the nodes value</param>
|
||||||
|
public TreeNode(Token value)
|
||||||
|
{
|
||||||
|
Parent = null;
|
||||||
|
Children = null;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the value of the node
|
||||||
|
/// </summary>
|
||||||
|
public Token NodeValue
|
||||||
|
{
|
||||||
|
get { return Value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the parent node of the current node, or null if it does not exist
|
||||||
|
/// </summary>
|
||||||
|
public TreeNode? ParentNode
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Parent == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all of the child nodes of the current node, or null if it odes not exist
|
||||||
|
/// </summary>
|
||||||
|
public List<TreeNode>? GetChildrenNodes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (Children == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public TreeNode? GetChildNode(int index)
|
||||||
|
{
|
||||||
|
if (Children == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (index < 0 || index >= Children.Count)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Children[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a child node to the current root node, if there are no children nodes a list is created
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Node">The value for the child node that is to be added</param>
|
||||||
|
public void AddChildNode(TreeNode Node)
|
||||||
|
{
|
||||||
|
if (Children == null)
|
||||||
|
{
|
||||||
|
Children = new()
|
||||||
|
{
|
||||||
|
Node
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Children.Add(Node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value that indicates if the given object is equal to the current instance of TreeNode
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="other">The object to compare to the current instance of TreeNode</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
142
MathEngine/MathEngine/Parser/Parser/Parser.cs
Normal file
142
MathEngine/MathEngine/Parser/Parser/Parser.cs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
using MathEngine.Parser.Tokeniser;
|
||||||
|
namespace MathEngine.Parser.Parser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the conversion from a list of Tokens representing Mathematical expression to List in Reverse Polish Notation form
|
||||||
|
/// </summary>
|
||||||
|
internal class Parser
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Return the Precdence of a given token operator
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="X">Token to get Precdence of</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is the operation left associative
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="X">Operation to check</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
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");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ''' Reverse the order of a given stack of Tokens
|
||||||
|
/// ''' </summary>
|
||||||
|
/// ''' <param name="Stack">Stack to reverse</param>
|
||||||
|
/// ''' <returns></returns>
|
||||||
|
private static Stack<Token> ReverseStackTok(Stack<Token> Stack)
|
||||||
|
{
|
||||||
|
Stack<Token> OutputStack = new (Stack.Count);
|
||||||
|
while ((Stack.Count != 0))
|
||||||
|
OutputStack.Push(Stack.Pop());
|
||||||
|
return OutputStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a list of tokens into a valid RPN expression stack
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Expression">List of tokens to parse</param>
|
||||||
|
/// <returns>Returns the Reverse polish notation form of the expression</returns>
|
||||||
|
static internal Stack<Token> Parse(List<Token> Expression)
|
||||||
|
{
|
||||||
|
//Temp holding stack for operators
|
||||||
|
Stack<Token> OperatorStack = new(Expression.Count/2);
|
||||||
|
//The final stack to return
|
||||||
|
Stack<Token> OutputStack = new(Expression.Count);
|
||||||
|
//Stack used to hold the number of input params to a function
|
||||||
|
//Stack<uint> ArityStack = new Stack<uint>();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
244
MathEngine/MathEngine/Parser/Tokeniser/Token.cs
Normal file
244
MathEngine/MathEngine/Parser/Tokeniser/Token.cs
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
namespace MathEngine.Parser.Tokeniser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the Token Type. The base for all manipulations
|
||||||
|
/// </summary>
|
||||||
|
internal struct Token
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the token for +
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Token Plus = new("+",Type.Addition,NumericType.NaN,0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the token for -
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Token Minus = new("-", Type.Subtraction, NumericType.NaN, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the token for *
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Token Multiply = new("*", Type.Multiplication, NumericType.NaN, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the token for /
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Token Divide = new("/", Type.Division, NumericType.NaN, 0);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum representing the token type
|
||||||
|
/// </summary>
|
||||||
|
internal enum Type
|
||||||
|
{
|
||||||
|
Numeric,
|
||||||
|
DecimalPoint,
|
||||||
|
Addition,
|
||||||
|
Subtraction,
|
||||||
|
Multiplication,
|
||||||
|
Division,
|
||||||
|
Exponentiation,
|
||||||
|
UnaryPlus,
|
||||||
|
UnaryMinus,
|
||||||
|
Operator,
|
||||||
|
Variable,
|
||||||
|
Function,
|
||||||
|
FunctionArgumentSeparator,
|
||||||
|
LeftParenthesis,
|
||||||
|
RightParenthesis,
|
||||||
|
OpenBracket,
|
||||||
|
CloseBracket,
|
||||||
|
OpenBrace,
|
||||||
|
CloseBrace
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Enum representing the numerical type of the token
|
||||||
|
/// </summary>
|
||||||
|
internal enum NumericType
|
||||||
|
{
|
||||||
|
Integer,
|
||||||
|
Decimal,
|
||||||
|
Complex,
|
||||||
|
NaN
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// String representing the value of the token
|
||||||
|
/// </summary>
|
||||||
|
private readonly string Value;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of token
|
||||||
|
/// </summary>
|
||||||
|
private readonly Type TokenType;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The numeric type of the token
|
||||||
|
/// </summary>
|
||||||
|
private readonly NumericType Numeric_Type;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The arity of the token
|
||||||
|
/// </summary>
|
||||||
|
private readonly uint Arity;
|
||||||
|
|
||||||
|
#region "Properties"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the value of the Token
|
||||||
|
/// </summary>
|
||||||
|
public string TokenValue
|
||||||
|
{
|
||||||
|
get { return Value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the type of the token
|
||||||
|
/// </summary>
|
||||||
|
public readonly Type Token_Type
|
||||||
|
{
|
||||||
|
get { return TokenType; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the numerical type of the token
|
||||||
|
/// </summary>
|
||||||
|
public readonly NumericType NumericalType
|
||||||
|
{
|
||||||
|
get { return Numeric_Type; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the arity of the token
|
||||||
|
/// </summary>
|
||||||
|
public uint FunctionArity
|
||||||
|
{
|
||||||
|
get { return Arity; }
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the Tokeniser.Token structure with a given TokenValue, TokenType, TokenNumericType and Arity
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="TokenValue">String representing the value of the token</param>
|
||||||
|
/// <param name="TokenType">The type that the token instance represents</param>
|
||||||
|
/// <param name="TokenNumericType">The numeric type of the token</param>
|
||||||
|
/// <param name="FunctionArity">The token Arity</param>
|
||||||
|
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
|
||||||
|
/// <summary>
|
||||||
|
/// Debug String; Used to give a string representation of a token
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public new string ToString()
|
||||||
|
{
|
||||||
|
return Value + "," + TokenType.ToString() + "," + Numeric_Type.ToString() + "," + Arity.ToString();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value that indicates whether a two Tokens are equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="X">First Token to compare</param>
|
||||||
|
/// <param name="Y">Second Token to compare</param>
|
||||||
|
/// <returns>Returns true if the two Tokens are equal and false otherwise</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a value that indicates whether a two Tokens are not equal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="X">First Token to compare</param>
|
||||||
|
/// <param name="Y">Second Token to compare</param>
|
||||||
|
/// <returns>Returns true if the two Tokens are not equal and false otherwise</returns>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calculates the HashCode for the current Token Instance
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return HashCode.Combine(Value, TokenType, Numeric_Type, Arity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs
Normal file
110
MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
namespace MathEngine.Parser.Tokeniser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the conversion of a Mathematical expression in string form to a List of Tokens
|
||||||
|
/// </summary>
|
||||||
|
static internal class Tokeniser
|
||||||
|
{
|
||||||
|
private static readonly List<char> Operators = new(new char[] { '+', '-', '*', '/', '^' });
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the next non-whitespace char or returns the null terminator is at EOS (End of stream)
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the token that represents the current character
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="curChar">The character to get the token of</param>
|
||||||
|
/// <returns>A token representing the current character, otherwise an exception is thrown</returns>
|
||||||
|
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)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tokenises a given Mathematical expression given as a string to a list of tokens
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Expression">Expression to tokenise</param>
|
||||||
|
/// <returns>A list of tokens representing the given expression, if the expression string is null or empty then an empty list is returned</returns>
|
||||||
|
static internal List<Token> Tokenise(string Expression)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Expression))
|
||||||
|
{
|
||||||
|
return new List<Token> { };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Cleanup whitespace
|
||||||
|
Expression = String.Concat(Expression.Where(c => !Char.IsWhiteSpace(c)));
|
||||||
|
Int32 currentIndex = -1;
|
||||||
|
//Example expression 1+1+Sin[x]
|
||||||
|
List<Token> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user