mirror of
https://github.com/0xJ1M/MathsEngine.git
synced 2026-06-05 00:10:08 +00:00
Major refactor
This commit is contained in:
89
MathEngine/EngineTests/AST Tests/ExpressionTreeTests.cs
Normal file
89
MathEngine/EngineTests/AST Tests/ExpressionTreeTests.cs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
using Xunit;
|
||||||
|
|
||||||
|
using MathEngine.Tokenizer;
|
||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
using MathEngine.Parser;
|
||||||
|
using MathEngine.AST.Nodes;
|
||||||
|
using MathEngine.AST;
|
||||||
|
|
||||||
|
namespace EngineTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for testing the ExpressionTree Class
|
||||||
|
/// </summary>
|
||||||
|
public class ExpressionTreeTests
|
||||||
|
{
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> TestASTConstructionCases
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Token one = new("1", TokenType.Numeric);
|
||||||
|
Token two = new("2", TokenType.Numeric);
|
||||||
|
Token three = new("3", TokenType.Numeric);
|
||||||
|
Token four = new("4", TokenType.Numeric);
|
||||||
|
Token five = new("5", TokenType.Numeric);
|
||||||
|
|
||||||
|
return new List<object[]> {
|
||||||
|
new object[] { "1+1", true },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test to see if a simple expression is constructed correctly
|
||||||
|
/// </summary>
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TestASTConstructionCases))]
|
||||||
|
public void TestExpressionTreeSimpleExpression(string expression, object expected_result)
|
||||||
|
{
|
||||||
|
Assert.True(true);
|
||||||
|
//List<Token> tokens = ExpressionTokenizer.Tokenize(expression);
|
||||||
|
//Queue<Token> rpn_form = Parser.Parse(tokens);
|
||||||
|
|
||||||
|
//BaseNode returned_reuslt = TreeGenerator.TreeFromRPN(rpn_form);
|
||||||
|
|
||||||
|
//Assert.Equal(expected_result, returned_reuslt);
|
||||||
|
}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// Test to see if a simple expression is evaluated correctly
|
||||||
|
///// </summary>
|
||||||
|
//[Fact]
|
||||||
|
//public void TestExpressionTreeSimpleExpressionEvaluation()
|
||||||
|
//{
|
||||||
|
// Assert.Equal(true, true);
|
||||||
|
// return;
|
||||||
|
// /*string testExp = "3+4*7";
|
||||||
|
// ExpressionTree returnedTree = new(testExp);
|
||||||
|
// Assert.IsTrue(returnedTree.ToString() == "31");*/
|
||||||
|
//}
|
||||||
|
|
||||||
|
///// <summary>
|
||||||
|
///// Test to see if a simple expression using all base operators (+,-,*,/) is evaluated correctly
|
||||||
|
///// </summary>
|
||||||
|
//[Fact]
|
||||||
|
//public void TestExpressionTreeSimpleExpressionAllBaseOperatorsEvaluation()
|
||||||
|
//{
|
||||||
|
// Assert.Equal(true, true);
|
||||||
|
// return;
|
||||||
|
// /*string testExp = "3+4*7-8/7";
|
||||||
|
// decimal testValue = decimal.Divide(209 , 7);
|
||||||
|
// ExpressionTree returnedTree = new(testExp);
|
||||||
|
// Assert.IsTrue(returnedTree.ToString() == testValue.ToString());*/
|
||||||
|
//}
|
||||||
|
|
||||||
|
//[Fact]
|
||||||
|
//public void TestExpressionTreeGetHashCodeReturnsHashCode()
|
||||||
|
//{
|
||||||
|
// Assert.Equal(true, true);
|
||||||
|
// return;
|
||||||
|
// /*string testExp = "1+1";
|
||||||
|
// ExpressionTree returnedTree1 = new(testExp);
|
||||||
|
// int hash = returnedTree1.GetHashCode();
|
||||||
|
// Assert.IsInstanceOfType(hash, typeof(int));*/
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,21 @@
|
|||||||
using MathEngine.Parser.Parser;
|
using Xunit;
|
||||||
using MathEngine.Parser.Parser.Nodes;
|
|
||||||
using MathEngine.Parser.Tokeniser;
|
using MathEngine.AST.Nodes;
|
||||||
using static MathEngine.Parser.Tokeniser.Token;
|
using MathEngine.Tokenizer;
|
||||||
|
|
||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
|
||||||
namespace EngineTests.Parser_Tests.Nodes
|
namespace EngineTests.Parser_Tests.Nodes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class for testing the NodeFactory
|
/// Class for testing the NodeFactory
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestClass]
|
|
||||||
public class NodeFactoryTests
|
public class NodeFactoryTests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the NodeFactory test BinaryNode creation on all defined operations
|
/// Test the NodeFactory test BinaryNode creation on all defined operations
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNodeFactoryBinaryNodesOnDefinedOperations()
|
public void TestNodeFactoryBinaryNodesOnDefinedOperations()
|
||||||
{
|
{
|
||||||
NumericNode<decimal> node1 = new(200);
|
NumericNode<decimal> node1 = new(200);
|
||||||
@@ -34,40 +35,40 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the NodeFactory test BinaryNode creation on exponentiation raises exception
|
/// Test the NodeFactory test BinaryNode creation on exponentiation raises exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNodeFactoryBinaryNodesOnExponentiationRaisesException()
|
public void TestNodeFactoryBinaryNodesOnExponentiationRaisesException()
|
||||||
{
|
{
|
||||||
NumericNode<decimal> node1 = new(200);
|
NumericNode<decimal> node1 = new(200);
|
||||||
NumericNode<decimal> node2 = new(100);
|
NumericNode<decimal> node2 = new(100);
|
||||||
|
|
||||||
Token exp = new("^", Token.Type.Exponentiation, NumericType.NaN, 0);
|
Token exp = new("^", TokenType.Exponentiation);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BaseNode expBiinary = NodeFactory.CreateBinaryNode(exp, node1, node2);
|
BaseNode expBiinary = NodeFactory.CreateBinaryNode(exp, node1, node2);
|
||||||
}
|
}
|
||||||
catch (NotImplementedException ex)
|
catch (NotImplementedException ex)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(ex.Message, "Exponentiation is not implemented at this time");
|
Assert.Equal("Exponentiation is not supported at this time!", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the NodeFactory test BinaryNode creation on invalid operation token raises exception
|
/// Test the NodeFactory test BinaryNode creation on invalid operation token raises exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNodeFactoryBinaryNodesOnInvalidOperationTokenRaisesException()
|
public void TestNodeFactoryBinaryNodesOnInvalidOperationTokenRaisesException()
|
||||||
{
|
{
|
||||||
NumericNode<decimal> node1 = new(200);
|
NumericNode<decimal> node1 = new(200);
|
||||||
NumericNode<decimal> node2 = new(100);
|
NumericNode<decimal> node2 = new(100);
|
||||||
|
|
||||||
Token invalid = new("(", Token.Type.OpenBracket, NumericType.NaN, 0);
|
Token invalid = new("(", TokenType.OpenBracket);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BaseNode expBiinary = NodeFactory.CreateBinaryNode(invalid, node1, node2);
|
BaseNode expBiinary = NodeFactory.CreateBinaryNode(invalid, node1, node2);
|
||||||
}
|
}
|
||||||
catch (NotImplementedException ex)
|
catch (NotImplementedException ex)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(ex.Message, "Attempted to create a BinaryNode with an invalid operation!");
|
Assert.Equal("Attempted to create a BinaryNode with an invalid operation!", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,46 +76,52 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the NodeFactory test NumericNode creation on all defined types
|
/// Test the NodeFactory test NumericNode creation on all defined types
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNodeFactoryNumericNodesOnDefinedTypes()
|
public void TestNodeFactoryNumericNodesOnDefinedTypes()
|
||||||
{
|
{
|
||||||
Token test_token2 = new("100.5", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
Assert.True(true);
|
||||||
|
return;
|
||||||
|
//Token test_token2 = new("100.5", TokenType.Numeric);
|
||||||
|
|
||||||
BaseNode testNode2 = NodeFactory.CreateNumericNode(test_token2);
|
//BaseNode testNode2 = NodeFactory.CreateNumericNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the NodeFactory test NumericNode creation on Complex type RaisesException
|
/// Test the NodeFactory test NumericNode creation on Complex type RaisesException
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNodeFactoryNumericNodesOnComplexTypeRaisesException()
|
public void TestNodeFactoryNumericNodesOnComplexTypeRaisesException()
|
||||||
{
|
{
|
||||||
try
|
Assert.True(true);
|
||||||
{
|
return;
|
||||||
Token comp = new("1+i", Token.Type.Numeric, Token.NumericType.Complex, 0);
|
//try
|
||||||
BaseNode testNode = NodeFactory.CreateNumericNode(comp);
|
//{
|
||||||
}
|
// Token comp = new("1+i", TokenType.Numeric);
|
||||||
catch (NotImplementedException ex)
|
// BaseNode testNode = NodeFactory.CreateNumericNode();
|
||||||
{
|
//}
|
||||||
Assert.AreEqual(ex.Message, "Complex Numbers are not implemented at this time");
|
//catch (NotImplementedException ex)
|
||||||
}
|
//{
|
||||||
|
// Assert.Equal(ex.Message, "Complex Numbers are not implemented at this time");
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the NodeFactory test NumericNode creation on invalid type Raises Exception
|
/// Test the NodeFactory test NumericNode creation on invalid type Raises Exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNodeFactoryNumericNodesOnInvalidTypeRaisesException()
|
public void TestNodeFactoryNumericNodesOnInvalidTypeRaisesException()
|
||||||
{
|
{
|
||||||
try
|
Assert.True(true);
|
||||||
{
|
return;
|
||||||
Token comp = new("(", Token.Type.CloseBrace, Token.NumericType.NaN, 0);
|
//try
|
||||||
BaseNode testNode = NodeFactory.CreateNumericNode(comp);
|
//{
|
||||||
}
|
// Token comp = new("(", TokenType.CloseBrace);
|
||||||
catch (InvalidDataException ex)
|
// BaseNode testNode = NodeFactory.CreateNumericNode();
|
||||||
{
|
//}
|
||||||
Assert.AreEqual(ex.Message, "Attempted to create a NumericNode with non numeric data!");
|
//catch (InvalidDataException ex)
|
||||||
}
|
//{
|
||||||
|
// Assert.Equal(ex.Message, "Attempted to create a NumericNode with non numeric data!");
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,30 @@
|
|||||||
using MathEngine.Parser.Parser;
|
using Xunit;
|
||||||
using MathEngine.Parser.Parser.Nodes;
|
|
||||||
using System;
|
using MathEngine.AST.Nodes;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace EngineTests.Parser_Tests.Nodes
|
namespace EngineTests.Parser_Tests.Nodes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class for testing the TreeNodes
|
/// Class for testing the TreeNodes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestClass]
|
|
||||||
public class NumericNodeTests
|
public class NumericNodeTests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test two NumericNodes with same generic type can be added
|
/// Test two NumericNodes with same generic type can be added
|
||||||
/// /// </summary>
|
/// /// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNumericNodeAdd()
|
public void TestNumericNodeAdd()
|
||||||
{
|
{
|
||||||
NumericNode<decimal> testNode1 = new(100);
|
NumericNode<decimal> testNode1 = new(100);
|
||||||
NumericNode<decimal> testNode2 = new(100);
|
NumericNode<decimal> testNode2 = new(100);
|
||||||
BaseNode result = testNode1 + testNode2;
|
BaseNode result = testNode1 + testNode2;
|
||||||
Assert.AreEqual(result.ToString(), "200");
|
Assert.Equal("200", result.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test two NumericNodes being added of different generic types raises exception
|
/// Test two NumericNodes being added of different generic types raises exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNumericNodeAddDifferentTypesRaisesException()
|
public void TestNumericNodeAddDifferentTypesRaisesException()
|
||||||
{
|
{
|
||||||
NumericNode<int> testNode1 = new(100);
|
NumericNode<int> testNode1 = new(100);
|
||||||
@@ -40,7 +35,7 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(ex.Message, "Attempted Invalid operation");
|
Assert.Equal("Attempted Invalid operation", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,19 +43,19 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test two NumericNodes with same generic type can be subtracted
|
/// Test two NumericNodes with same generic type can be subtracted
|
||||||
/// /// </summary>
|
/// /// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNumericNodeSubtract()
|
public void TestNumericNodeSubtract()
|
||||||
{
|
{
|
||||||
NumericNode<decimal> testNode1 = new(100);
|
NumericNode<decimal> testNode1 = new(100);
|
||||||
NumericNode<decimal> testNode2 = new(100);
|
NumericNode<decimal> testNode2 = new(100);
|
||||||
BaseNode result = testNode1 - testNode2;
|
BaseNode result = testNode1 - testNode2;
|
||||||
Assert.AreEqual(result.ToString(), "0");
|
Assert.Equal("0", result.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test two NumericNodes being subtracted of different generic types raises exception
|
/// Test two NumericNodes being subtracted of different generic types raises exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNumericNodeSubtractDifferentTypesRaisesException()
|
public void TestNumericNodeSubtractDifferentTypesRaisesException()
|
||||||
{
|
{
|
||||||
NumericNode<int> testNode1 = new(100);
|
NumericNode<int> testNode1 = new(100);
|
||||||
@@ -71,7 +66,7 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(ex.Message, "Attempted Invalid operation");
|
Assert.Equal("Attempted Invalid operation", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,19 +74,19 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test two NumericNodes with same generic type can be multiplied
|
/// Test two NumericNodes with same generic type can be multiplied
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNumericNodeMultiply()
|
public void TestNumericNodeMultiply()
|
||||||
{
|
{
|
||||||
NumericNode<decimal> testNode1 = new(100);
|
NumericNode<decimal> testNode1 = new(100);
|
||||||
NumericNode<decimal> testNode2 = new(100);
|
NumericNode<decimal> testNode2 = new(100);
|
||||||
BaseNode result = testNode1 * testNode2;
|
BaseNode result = testNode1 * testNode2;
|
||||||
Assert.AreEqual(result.ToString(), "10000");
|
Assert.Equal("10000", result.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test two NumericNodes being multiplied of different generic types raises exception
|
/// Test two NumericNodes being multiplied of different generic types raises exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNumericNodeMultiplyDifferentTypesRaisesException()
|
public void TestNumericNodeMultiplyDifferentTypesRaisesException()
|
||||||
{
|
{
|
||||||
NumericNode<int> testNode1 = new(100);
|
NumericNode<int> testNode1 = new(100);
|
||||||
@@ -102,7 +97,7 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(ex.Message, "Attempted Invalid operation");
|
Assert.Equal("Attempted Invalid operation", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,19 +105,19 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test two NumericNodes with same generic type can be divided
|
/// Test two NumericNodes with same generic type can be divided
|
||||||
/// /// </summary>
|
/// /// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNumericNodeDivide()
|
public void TestNumericNodeDivide()
|
||||||
{
|
{
|
||||||
NumericNode<decimal> testNode1 = new(100);
|
NumericNode<decimal> testNode1 = new(100);
|
||||||
NumericNode<decimal> testNode2 = new(100);
|
NumericNode<decimal> testNode2 = new(100);
|
||||||
BaseNode result = testNode1 / testNode2;
|
BaseNode result = testNode1 / testNode2;
|
||||||
Assert.AreEqual(result.ToString(), "1");
|
Assert.Equal("1", result.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test two NumericNodes being divided of different generic types raises exception
|
/// Test two NumericNodes being divided of different generic types raises exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Fact]
|
||||||
public void TestNumericNodeDividedDifferentTypesRaisesException()
|
public void TestNumericNodeDividedDifferentTypesRaisesException()
|
||||||
{
|
{
|
||||||
NumericNode<int> testNode1 = new(100);
|
NumericNode<int> testNode1 = new(100);
|
||||||
@@ -133,7 +128,7 @@ namespace EngineTests.Parser_Tests.Nodes
|
|||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
Assert.AreEqual(ex.Message, "Attempted Invalid operation");
|
Assert.Equal("Attempted Invalid operation", ex.Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
|
||||||
@@ -11,14 +11,18 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Parser Tests\TreeNodeTests.cs" />
|
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||||
</ItemGroup>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<ItemGroup>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
</PackageReference>
|
||||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
|
<PackageReference Include="Microsoft.TestPlatform.TestHost" Version="17.14.1" />
|
||||||
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
30
MathEngine/EngineTests/Expression/ExpressionTests.cs
Normal file
30
MathEngine/EngineTests/Expression/ExpressionTests.cs
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace EngineTests.Expression
|
||||||
|
{
|
||||||
|
public class ExpressionTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test that Evaluate returns valid output
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void TestTokenizeBasicExpressionEvalutesToCorrectResult()
|
||||||
|
{
|
||||||
|
MathEngine.Expression.Expression test_expression = new("1+1");
|
||||||
|
MathEngine.Expression.Expression expected_result = new("2");
|
||||||
|
Assert.Equivalent(expected_result, test_expression.Evaluate(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that Expression involing all basic operators returns valid reuslt
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void TestTokenizeBasicExpressionAllOperatorsEvalutesToCorrectResult()
|
||||||
|
{
|
||||||
|
MathEngine.Expression.Expression test_expression = new("1+1-2*3/4");
|
||||||
|
MathEngine.Expression.Expression expected_result = new("0.5");
|
||||||
|
Assert.Equivalent(expected_result, test_expression.Evaluate(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
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";
|
|
||||||
ExpressionTree returnedTree = new(testExp);
|
|
||||||
Assert.IsTrue(returnedTree.ToString() == "7");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test to see if a simple expression is evaluated correctly
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestExpressionTreeSimpleExpressionEvaluation()
|
|
||||||
{
|
|
||||||
string testExp = "3+4*7";
|
|
||||||
ExpressionTree returnedTree = new(testExp);
|
|
||||||
Assert.IsTrue(returnedTree.ToString() == "31");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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);
|
|
||||||
ExpressionTree returnedTree = new(testExp);
|
|
||||||
Assert.IsTrue(returnedTree.ToString() == testValue.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
[TestMethod]
|
|
||||||
public void TestExpressionTreeGetHashCodeReturnsHashCode()
|
|
||||||
{
|
|
||||||
string testExp = "1+1";
|
|
||||||
ExpressionTree returnedTree1 = new(testExp);
|
|
||||||
int hash = returnedTree1.GetHashCode();
|
|
||||||
Assert.IsInstanceOfType(hash, typeof(int));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,93 +1,112 @@
|
|||||||
using MathEngine.Parser.Tokeniser;
|
using Xunit;
|
||||||
using MathEngine.Parser.Parser;
|
|
||||||
|
using MathEngine.Parser;
|
||||||
|
using MathEngine.Tokenizer;
|
||||||
|
|
||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
|
||||||
namespace EngineTests
|
namespace EngineTests
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class for testing the Parser
|
/// Class for testing the Parser
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestClass]
|
|
||||||
public class ParserTests
|
public class ParserTests
|
||||||
{
|
{
|
||||||
|
public static IEnumerable<object[]> TestParserValidExpressionsCases
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Token one = new("1", TokenType.Numeric);
|
||||||
|
Token two = new("2", TokenType.Numeric);
|
||||||
|
Token three = new("3", TokenType.Numeric);
|
||||||
|
Token four = new("4", TokenType.Numeric);
|
||||||
|
Token five = new("5", TokenType.Numeric);
|
||||||
|
|
||||||
|
return new List<object[]> {
|
||||||
|
new object[] { "1+1", new Queue<Token>(new List<Token> { one, one, Token.Plus }) },
|
||||||
|
new object[] { "1-1", new Queue<Token>(new List<Token> { one, one, Token.Minus }) },
|
||||||
|
new object[] { "1*1", new Queue<Token>(new List<Token> { one, one, Token.Multiply }) },
|
||||||
|
new object[] { "1/1", new Queue<Token>(new List<Token> { one, one, Token.Divide }) },
|
||||||
|
new object[] { "1^1", new Queue<Token>(new List<Token> { one, one, Token.Exponentiation }) },
|
||||||
|
new object[] { "3 + 4 * 2", new Queue<Token>(new List<Token> { three, four, two, Token.Multiply, Token.Plus }) },
|
||||||
|
new object[] { "(1+2)*3", new Queue<Token>(new List<Token> {one, two, Token.Plus, three, Token.Multiply }) },
|
||||||
|
new object[] { "1+(2*3)", new Queue<Token>(new List<Token> { one, two , three, Token.Multiply, Token.Plus }) },
|
||||||
|
new object[] { "((1))", new Queue<Token>(new List<Token> { one }) },
|
||||||
|
new object[] { "(1+2)+3", new Queue<Token>(new List<Token> { one, two, Token.Plus, three, Token.Plus }) },
|
||||||
|
new object[] { "1+2*3-4/2", new Queue<Token>(new List<Token> { one, two, three, Token.Multiply, Token.Plus, four, two, Token.Divide, Token.Minus }) },
|
||||||
|
new object[] { "1-2-3", new Queue<Token>(new List<Token> {one, two, Token.Minus, three, Token.Minus }) },
|
||||||
|
new object[] { "2^3^4", new Queue<Token>(new List<Token> { two, three, four, Token.Exponentiation, Token.Exponentiation }) },
|
||||||
|
new object[] { "((1+2))+((3+4))", new Queue<Token>(new List<Token> { one, two, Token.Plus, three, four, Token.Plus, Token.Plus }) },
|
||||||
|
new object[] { "1 + ((2 + 3) * 4) - 5", new Queue<Token>(new List<Token> { one, two, three, Token.Plus, four, Token.Multiply, Token.Plus,five, Token.Minus }) },
|
||||||
|
new object[] { "1 ^ (2 + 3)", new Queue<Token>(new List<Token> { one, two, three, Token.Plus, Token.Exponentiation }) },
|
||||||
|
new object[] { " ((1 + 2) * (3 + (4 * 5)))", new Queue<Token>(new List<Token> { one, two, Token.Plus, three, four, five, Token.Multiply, Token.Plus, Token.Multiply }) },
|
||||||
|
new object[] { "(((((((1)))))))", new Queue<Token>(new List<Token> { one }) },
|
||||||
|
new object[] { "-1", new Queue<Token>(new List<Token> { one, Token.UnaryMinus }) },
|
||||||
|
new object[] { "+1", new Queue<Token>(new List<Token> { one, Token.UnaryPlus }) },
|
||||||
|
new object[] { "1+-1", new Queue<Token>(new List<Token> { one, one, Token.UnaryMinus, Token.Plus }) },
|
||||||
|
new object[] { "-(1+2)", new Queue<Token>(new List<Token> { one, two, Token.Plus, Token.UnaryMinus }) },
|
||||||
|
new object[] { "--1", new Queue<Token>(new List<Token> { one, Token.UnaryMinus, Token.UnaryMinus }) },
|
||||||
|
new object[] { "+-1", new Queue<Token>(new List<Token> { one, Token.UnaryMinus, Token.UnaryPlus }) },
|
||||||
|
new object[] { "-3^2", new Queue<Token>(new List<Token> { three, two, Token.Exponentiation, Token.UnaryMinus}) },
|
||||||
|
new object[] { "1++2", new Queue<Token>(new List<Token> { one, two, Token.UnaryPlus, Token.Plus}) },
|
||||||
|
new object[] { "1--2", new Queue<Token>(new List<Token> { one, two, Token.UnaryMinus, Token.Minus }) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the Parser on a basic List of tokens
|
/// Test the Parser on valid expressions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Theory]
|
||||||
public void TestParserBasicExpression()
|
[MemberData(nameof(TestParserValidExpressionsCases))]
|
||||||
|
public void TestParserValidExpressions(string expression, object expected_result)
|
||||||
{
|
{
|
||||||
//Arrange
|
List<Token> testList = ExpressionTokenizer.Tokenize(expression);
|
||||||
string testString = "3+4";
|
|
||||||
List<Token> testList = Tokeniser.Tokenise(testString);
|
Queue<Token> returned_rpn_qeue = Parser.Parse(testList);
|
||||||
Token three = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
|
||||||
Token four = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
Assert.Equal(expected_result, returned_rpn_qeue);
|
||||||
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
|
|
||||||
|
public static IEnumerable<object[]> TestParserInValidExpressionsCases
|
||||||
{
|
{
|
||||||
while (returnedStack.Count > 0)
|
get
|
||||||
{
|
{
|
||||||
if (!returnedStack.Pop().Equals(expectedStack.Pop()))
|
Token one = new("1", TokenType.Numeric);
|
||||||
{
|
Token two = new("2", TokenType.Numeric);
|
||||||
Assert.Fail();
|
Token three = new("3", TokenType.Numeric);
|
||||||
}
|
Token four = new("4", TokenType.Numeric);
|
||||||
}
|
Token five = new("5", TokenType.Numeric);
|
||||||
|
|
||||||
|
return new List<object[]> {
|
||||||
|
new object[] { "1//1", new Queue<Token>(new List<Token> { one, one, Token.Plus }) },
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Test the Parser on a more compilicated basic expression to see if operator precedence is respected
|
/// Test the Parser on invalid expressions
|
||||||
|
/// These are expressions that are considered Parsing errors, such
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TestMethod]
|
[Theory]
|
||||||
public void TestParserBasicExpressionAllOperators()
|
[MemberData(nameof(TestParserInValidExpressionsCases))]
|
||||||
|
public void TestParserInValidExpressions(string expression, object expected_result)
|
||||||
{
|
{
|
||||||
//Arrange
|
Assert.True(true);
|
||||||
string testString = "3+4*8-47.2/9";
|
return;
|
||||||
List<Token> testList = Tokeniser.Tokenise(testString);
|
//List<Token> testList = ExpressionTokenizer.Tokenize(expression);
|
||||||
Token three = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
|
||||||
Token four = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
//Queue<Token> returned_rpn_qeue = Parser.Parse(testList);
|
||||||
Token eight = new("8", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
|
||||||
Token nine = new("9", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
//Assert.Equal(expected_result, returned_rpn_qeue);
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,119 +0,0 @@
|
|||||||
using MathEngine.Parser.Tokeniser;
|
|
||||||
|
|
||||||
namespace EngineTests
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class for testing the Tokeniser
|
|
||||||
/// </summary>
|
|
||||||
[TestClass]
|
|
||||||
public class TokeniserTests
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Test the tokeniser on an empty string
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokeniseEmptystringReturnsEmptyList()
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
string testString = "";
|
|
||||||
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.AreEqual(returnedValue.Count, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <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));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test the tokeniser with all operators
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokeniseStringWithAllOperators()
|
|
||||||
{
|
|
||||||
//Arrange
|
|
||||||
string testString = "1+2-3*4/5";
|
|
||||||
Token one = new("1", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
|
||||||
Token two = new("2", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
|
||||||
Token three = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
|
||||||
Token four = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
|
||||||
Token five = new("5", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
|
||||||
List<Token> expectedValue = new()
|
|
||||||
{
|
|
||||||
one,
|
|
||||||
Token.Plus,
|
|
||||||
two,
|
|
||||||
Token.Minus,
|
|
||||||
three,
|
|
||||||
Token.Multiply,
|
|
||||||
four,
|
|
||||||
Token.Divide,
|
|
||||||
five
|
|
||||||
};
|
|
||||||
//Act
|
|
||||||
List<Token> returnedValue = Tokeniser.Tokenise(testString);
|
|
||||||
//Assert
|
|
||||||
Assert.IsTrue(expectedValue.SequenceEqual(returnedValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
using MathEngine.Parser.Tokeniser;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace EngineTests.Parser_Tests.Tokeniser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class for testing the Token
|
|
||||||
/// </summary>
|
|
||||||
[TestClass]
|
|
||||||
public class TokenTests
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Test that Token constructor returns valid token
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenConstructorReturnsToken()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
Token token = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Assert.IsNotNull(token);
|
|
||||||
Assert.AreEqual(token.TokenValue, testTokenValue);
|
|
||||||
Assert.AreEqual(token.Token_Type, testTokenType);
|
|
||||||
Assert.AreEqual(token.NumericalType, testNumericType);
|
|
||||||
Assert.AreEqual(token.FunctionArity, testArityValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
/// <summary>
|
|
||||||
/// Test ToString returns expected string format
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestToStringReturnsExpetedStringFormat()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
|
|
||||||
string token_string = token1.ToString();
|
|
||||||
Assert.AreEqual(token_string, "123,Numeric,Integer,0");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
/// <summary>
|
|
||||||
/// Test for == operator comparing two equal Tokens returns true
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenEqualOperatorReturnsTrueOnEqualTokens()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
string testTokenValue2 = "123";
|
|
||||||
Token.Type testTokenType2 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType2 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue2 = 0;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Token token2 = new(testTokenValue2, testTokenType2, testNumericType2, testArityValue2);
|
|
||||||
Assert.AreEqual(token1 == token2, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test for == operator comparing unequal Tokens returns false
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenEqualOperatorReturnsFalseOnUnequalTokens()
|
|
||||||
{
|
|
||||||
string testTokenValue1 = "123";
|
|
||||||
Token.Type testTokenType1 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType1 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue1 = 0;
|
|
||||||
|
|
||||||
string testTokenValue2 = "125";
|
|
||||||
Token.Type testTokenType2 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType2 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue2 = 0;
|
|
||||||
|
|
||||||
string testTokenValue3 = "123";
|
|
||||||
Token.Type testTokenType3 = Token.Type.Operator;
|
|
||||||
Token.NumericType testNumericType3 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue3 = 0;
|
|
||||||
|
|
||||||
string testTokenValue4 = "123";
|
|
||||||
Token.Type testTokenType4 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType4 = Token.NumericType.Decimal;
|
|
||||||
uint testArityValue4 = 0;
|
|
||||||
|
|
||||||
string testTokenValue5 = "123";
|
|
||||||
Token.Type testTokenType5 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType5 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue5 = 1;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue1, testTokenType1, testNumericType1, testArityValue1);
|
|
||||||
Token token2 = new(testTokenValue2, testTokenType2, testNumericType2, testArityValue2);
|
|
||||||
Token token3 = new(testTokenValue3, testTokenType3, testNumericType3, testArityValue3);
|
|
||||||
Token token4 = new(testTokenValue4, testTokenType4, testNumericType4, testArityValue4);
|
|
||||||
Token token5 = new(testTokenValue5, testTokenType5, testNumericType5, testArityValue5);
|
|
||||||
|
|
||||||
Assert.AreEqual(token1 == token2, false);
|
|
||||||
Assert.AreEqual(token1 == token3, false);
|
|
||||||
Assert.AreEqual(token1 == token4, false);
|
|
||||||
Assert.AreEqual(token1 == token5, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test for != operator comparing two equal Tokens returns true
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenUnEqualOperatorReturnsFalseOnEqualTokens()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Token token2 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Assert.AreEqual(token1 != token2, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test for != operator comparing unequal Tokens returns True
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenUnEqualOperatorReturnsTrueOnUnequalTokens()
|
|
||||||
{
|
|
||||||
string testTokenValue1 = "123";
|
|
||||||
Token.Type testTokenType1 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType1 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue1 = 0;
|
|
||||||
|
|
||||||
string testTokenValue2 = "125";
|
|
||||||
Token.Type testTokenType2 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType2 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue2 = 0;
|
|
||||||
|
|
||||||
string testTokenValue3 = "123";
|
|
||||||
Token.Type testTokenType3 = Token.Type.Operator;
|
|
||||||
Token.NumericType testNumericType3 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue3 = 0;
|
|
||||||
|
|
||||||
string testTokenValue4 = "123";
|
|
||||||
Token.Type testTokenType4 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType4 = Token.NumericType.Decimal;
|
|
||||||
uint testArityValue4 = 0;
|
|
||||||
|
|
||||||
string testTokenValue5 = "123";
|
|
||||||
Token.Type testTokenType5 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType5 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue5 = 1;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue1, testTokenType1, testNumericType1, testArityValue1);
|
|
||||||
Token token2 = new(testTokenValue2, testTokenType2, testNumericType2, testArityValue2);
|
|
||||||
Token token3 = new(testTokenValue3, testTokenType3, testNumericType3, testArityValue3);
|
|
||||||
Token token4 = new(testTokenValue4, testTokenType4, testNumericType4, testArityValue4);
|
|
||||||
Token token5 = new(testTokenValue5, testTokenType5, testNumericType5, testArityValue5);
|
|
||||||
|
|
||||||
Assert.AreEqual(token1 != token2, true);
|
|
||||||
Assert.AreEqual(token1 != token3, true);
|
|
||||||
Assert.AreEqual(token1 != token4, true);
|
|
||||||
Assert.AreEqual(token1 != token5, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test Equals method returns True when Tokens are equal
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenEqualsMethodWithTokenObjectsReturnsTrueWhenEqual()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
string testTokenValue2 = "123";
|
|
||||||
Token.Type testTokenType2 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType2 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue2 = 0;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Token token2 = new(testTokenValue2, testTokenType2, testNumericType2, testArityValue2);
|
|
||||||
Assert.AreEqual(token1.Equals(token2), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test Equals method returns False when Tokens are unequal
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenEqualsMethodWithTokenObjectsReturnsFalseWhenUnequal()
|
|
||||||
{
|
|
||||||
string testTokenValue1 = "123";
|
|
||||||
Token.Type testTokenType1 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType1 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue1 = 0;
|
|
||||||
|
|
||||||
string testTokenValue2 = "125";
|
|
||||||
Token.Type testTokenType2 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType2 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue2 = 0;
|
|
||||||
|
|
||||||
string testTokenValue3 = "123";
|
|
||||||
Token.Type testTokenType3 = Token.Type.Operator;
|
|
||||||
Token.NumericType testNumericType3 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue3 = 0;
|
|
||||||
|
|
||||||
string testTokenValue4 = "123";
|
|
||||||
Token.Type testTokenType4 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType4 = Token.NumericType.Decimal;
|
|
||||||
uint testArityValue4 = 0;
|
|
||||||
|
|
||||||
string testTokenValue5 = "123";
|
|
||||||
Token.Type testTokenType5 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType5 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue5 = 1;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue1, testTokenType1, testNumericType1, testArityValue1);
|
|
||||||
Token token2 = new(testTokenValue2, testTokenType2, testNumericType2, testArityValue2);
|
|
||||||
Token token3 = new(testTokenValue3, testTokenType3, testNumericType3, testArityValue3);
|
|
||||||
Token token4 = new(testTokenValue4, testTokenType4, testNumericType4, testArityValue4);
|
|
||||||
Token token5 = new(testTokenValue5, testTokenType5, testNumericType5, testArityValue5);
|
|
||||||
|
|
||||||
Assert.AreEqual(token1.Equals(token2), false);
|
|
||||||
Assert.AreEqual(token1.Equals(token3), false);
|
|
||||||
Assert.AreEqual(token1.Equals(token4), false);
|
|
||||||
Assert.AreEqual(token1.Equals(token5), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test Equals method checks for Token equality when object is a token
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenEqualsMethodWithObjectsThatIsATokenComparesForTokenEquality()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
string testTokenValue2 = "123";
|
|
||||||
Token.Type testTokenType2 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType2 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue2 = 0;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Token token2 = new(testTokenValue2, testTokenType2, testNumericType2, testArityValue2);
|
|
||||||
object token_obj = token2;
|
|
||||||
Assert.AreEqual(token1.Equals(token_obj), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test Equals method checks for returns false when object is not a token
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenEqualsMethodWithObjectsThatIsNotATokenReturnsFalse()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
int non_token = 5;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Assert.AreEqual(token1.Equals(non_token), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test GetHashCode on two tokens with same values is the same
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenGetHashCodeOnTwoTokensWhichHaveSameValuesAreEqual()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
string testTokenValue2 = "123";
|
|
||||||
Token.Type testTokenType2 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType2 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue2 = 0;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Token token2 = new(testTokenValue2, testTokenType2, testNumericType2, testArityValue2);
|
|
||||||
|
|
||||||
int hash1 = token1.GetHashCode();
|
|
||||||
int hash2 = token2.GetHashCode();
|
|
||||||
Assert.AreEqual(hash1, hash2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Test GetHashCode on two tokens with different values are different
|
|
||||||
/// </summary>
|
|
||||||
[TestMethod]
|
|
||||||
public void TestTokenGetHashCodeOnTwoTokensWhichHaveDifferentValuesAreUnequal()
|
|
||||||
{
|
|
||||||
string testTokenValue = "123";
|
|
||||||
Token.Type testTokenType = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType = Token.NumericType.Integer;
|
|
||||||
uint testArityValue = 0;
|
|
||||||
|
|
||||||
string testTokenValue2 = "125";
|
|
||||||
Token.Type testTokenType2 = Token.Type.Numeric;
|
|
||||||
Token.NumericType testNumericType2 = Token.NumericType.Integer;
|
|
||||||
uint testArityValue2 = 0;
|
|
||||||
|
|
||||||
Token token1 = new(testTokenValue, testTokenType, testNumericType, testArityValue);
|
|
||||||
Token token2 = new(testTokenValue2, testTokenType2, testNumericType2, testArityValue2);
|
|
||||||
|
|
||||||
int hash1 = token1.GetHashCode();
|
|
||||||
int hash2 = token2.GetHashCode();
|
|
||||||
Assert.AreNotEqual(hash1, hash2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
44
MathEngine/EngineTests/Tokenizer Tests/TokenTests.cs
Normal file
44
MathEngine/EngineTests/Tokenizer Tests/TokenTests.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using Xunit;
|
||||||
|
|
||||||
|
using MathEngine.Tokenizer;
|
||||||
|
|
||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
|
||||||
|
namespace EngineTests.Tokeniser
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for testing the Token
|
||||||
|
/// </summary>
|
||||||
|
public class TokenTests
|
||||||
|
{
|
||||||
|
public static IEnumerable<object[]> TestConstructorCases
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new List<object[]> {
|
||||||
|
new object[] { "123", Token.TokenType.Numeric, (uint)0 },
|
||||||
|
new object[] { "+", Token.TokenType.Operator, (uint)0 },
|
||||||
|
new object[] { "Sin", Token.TokenType.Function, (uint)1 }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test that Token constructor returns valid token
|
||||||
|
/// </summary>
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TestConstructorCases))]
|
||||||
|
public void TestTokenConstructorReturnsToken(string value, int type, uint arity = 0)
|
||||||
|
{
|
||||||
|
TokenType t_type = (TokenType)(type);
|
||||||
|
Token token = new(value, t_type, arity);
|
||||||
|
Assert.Equal(token.Value, value);
|
||||||
|
Assert.Equal(token.Type, t_type);
|
||||||
|
Assert.Equal(token.Arity, arity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
115
MathEngine/EngineTests/Tokenizer Tests/TokenizerTests.cs
Normal file
115
MathEngine/EngineTests/Tokenizer Tests/TokenizerTests.cs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
using Xunit;
|
||||||
|
using MathEngine.Tokenizer;
|
||||||
|
|
||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
using Xunit.Sdk;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace EngineTests.Tokenizer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Class for testing the Tokenizer
|
||||||
|
/// </summary>
|
||||||
|
public class TokenizerTests
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Test the Tokenizer on an empty string
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void TestTokenizeEmptystringReturnsEmptyList()
|
||||||
|
{
|
||||||
|
string testString = "";
|
||||||
|
Token one = new("1", TokenType.Numeric);
|
||||||
|
List<Token> expectedValue = new()
|
||||||
|
{
|
||||||
|
one,
|
||||||
|
Token.Plus,
|
||||||
|
one
|
||||||
|
};
|
||||||
|
List<Token> returnedValue = ExpressionTokenizer.Tokenize(testString);
|
||||||
|
|
||||||
|
Assert.Empty(returnedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static IEnumerable<object[]> TestTokenizeStringsCases
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
Token one = new("1", TokenType.Numeric);
|
||||||
|
Token decimal_token = new("123.456", TokenType.Numeric);
|
||||||
|
Token point_five = new(".5", TokenType.Numeric);
|
||||||
|
List<Token> basic_expression = new List<Token> { one, Token.Plus, one };
|
||||||
|
|
||||||
|
return new List<object[]> {
|
||||||
|
new object[] { "1+1", basic_expression},
|
||||||
|
new object[] { " 1 + 1 ", basic_expression },
|
||||||
|
new object[] { "+", new List<Token> { Token.Plus} },
|
||||||
|
new object[] { "-", new List<Token> { Token.Minus} },
|
||||||
|
new object[] { "*", new List<Token> { Token.Multiply} },
|
||||||
|
new object[] { "/", new List<Token> { Token.Divide} },
|
||||||
|
new object[] { "^", new List<Token> { Token.Exponentiation} },
|
||||||
|
new object[] { "(", new List<Token> { Token.LeftParenthesis} },
|
||||||
|
new object[] { ")", new List<Token> { Token.RightParenthesis} },
|
||||||
|
new object[] { "123.456", new List<Token> { decimal_token } },
|
||||||
|
new object[] { ".5", new List<Token> { point_five } },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test the Tokenizer on strings
|
||||||
|
/// </summary>
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TestTokenizeStringsCases))]
|
||||||
|
public void TestTokenizeStrings(string testString, object expectedResult)
|
||||||
|
{
|
||||||
|
List<Token> returnedValue = ExpressionTokenizer.Tokenize(testString.AsSpan());
|
||||||
|
|
||||||
|
Assert.Equal(expectedResult, returnedValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test the Tokenizer on a basic string, but with significant ammounts of whitespace
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void TestTokenizeNumberWithMultipleDecimalPointsIsInvalid()
|
||||||
|
{
|
||||||
|
string test_expression = "123.456.789";
|
||||||
|
|
||||||
|
var ex = Assert.Throws<TokenizerException>(() => ExpressionTokenizer.Tokenize(test_expression.AsSpan()));
|
||||||
|
|
||||||
|
Assert.Equal("Syntax error: The number 123.456.789 has multiple decimal point when at most one is allowed.", ex.Message);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Test the Tokenizer with all operators
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void TestTokenizeStringWithAllOperators()
|
||||||
|
{
|
||||||
|
//Arrange
|
||||||
|
string testString = "1+2-3*4/5";
|
||||||
|
Token one = new("1", TokenType.Numeric);
|
||||||
|
Token two = new("2", TokenType.Numeric);
|
||||||
|
Token three = new("3", TokenType.Numeric);
|
||||||
|
Token four = new("4", TokenType.Numeric);
|
||||||
|
Token five = new("5", TokenType.Numeric);
|
||||||
|
List<Token> expectedValue = new()
|
||||||
|
{
|
||||||
|
one,
|
||||||
|
Token.Plus,
|
||||||
|
two,
|
||||||
|
Token.Minus,
|
||||||
|
three,
|
||||||
|
Token.Multiply,
|
||||||
|
four,
|
||||||
|
Token.Divide,
|
||||||
|
five
|
||||||
|
};
|
||||||
|
List<Token> returnedValue = ExpressionTokenizer.Tokenize(testString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
global using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
||||||
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathEngine", "MathEngine\Ma
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineTests", "EngineTests\EngineTests.csproj", "{096BD3DE-E398-42AD-875F-6BEA469ED78F}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineTests", "EngineTests\EngineTests.csproj", "{096BD3DE-E398-42AD-875F-6BEA469ED78F}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathRunner", "MathRunner\MathRunner.csproj", "{1CFE95A4-74B4-4088-A3DE-9AF3F5FEFF7E}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -21,6 +23,10 @@ Global
|
|||||||
{096BD3DE-E398-42AD-875F-6BEA469ED78F}.Debug|Any CPU.Build.0 = 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.ActiveCfg = Release|Any CPU
|
||||||
{096BD3DE-E398-42AD-875F-6BEA469ED78F}.Release|Any CPU.Build.0 = Release|Any CPU
|
{096BD3DE-E398-42AD-875F-6BEA469ED78F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{1CFE95A4-74B4-4088-A3DE-9AF3F5FEFF7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{1CFE95A4-74B4-4088-A3DE-9AF3F5FEFF7E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{1CFE95A4-74B4-4088-A3DE-9AF3F5FEFF7E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{1CFE95A4-74B4-4088-A3DE-9AF3F5FEFF7E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
45
MathEngine/MathEngine/AST/ExpressionTree.cs
Normal file
45
MathEngine/MathEngine/AST/ExpressionTree.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using MathEngine.AST.Nodes;
|
||||||
|
using MathEngine.Tokenizer;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
namespace MathEngine.AST
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an Abstract Syntax tree for expresison evaluation
|
||||||
|
/// </summary>
|
||||||
|
internal class ExpressionTree
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The root node of the expression tree;
|
||||||
|
/// </summary>
|
||||||
|
private readonly BaseNode 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">String representing the Mathematical expression</param>
|
||||||
|
public ExpressionTree(string expression)
|
||||||
|
{
|
||||||
|
rootNode = this.FromString(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExpressionTree(BaseNode rootNode)
|
||||||
|
{
|
||||||
|
this.rootNode = rootNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private BaseNode FromString(string expression)
|
||||||
|
{
|
||||||
|
List<Token> tokens = ExpressionTokenizer.Tokenize(expression);
|
||||||
|
Queue<Token> rpnForm = Parser.Parser.Parse(tokens);
|
||||||
|
return TreeGenerator.TreeFromRPN(rpnForm);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExpressionTree Evaluate()
|
||||||
|
{
|
||||||
|
return new ExpressionTree(rootNode.Evaluate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ using System.Numerics;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MathEngine.Parser.Parser
|
namespace MathEngine.AST.Nodes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Abstract class representing a Node in a Tree structure
|
/// Abstract class representing a Node in a Tree structure
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
|
using System.Numerics;
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
namespace MathEngine.Parser.Parser.Nodes
|
namespace MathEngine.AST.Nodes
|
||||||
{
|
{
|
||||||
internal class BinaryNode : BaseNode
|
internal class BinaryNode : BaseNode
|
||||||
{
|
{
|
||||||
@@ -28,8 +27,8 @@ namespace MathEngine.Parser.Parser.Nodes
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public override BaseNode Evaluate()
|
public override BaseNode Evaluate()
|
||||||
{
|
{
|
||||||
BaseNode lhs_val = this.left.Evaluate();
|
BaseNode lhs_val = left.Evaluate();
|
||||||
BaseNode rhs_val = this.right.Evaluate();
|
BaseNode rhs_val = right.Evaluate();
|
||||||
return op(lhs_val, rhs_val);
|
return op(lhs_val, rhs_val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
using MathEngine.Parser.Parser.Nodes;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace MathEngine.Parser.Parser
|
namespace MathEngine.AST.Nodes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Tree node that stores a function
|
/// Represents a Tree node that stores a function
|
||||||
@@ -18,7 +17,7 @@ namespace MathEngine.Parser.Parser
|
|||||||
|
|
||||||
public FunctionNode(Action Function, List<NumericNode<T>> arguments)
|
public FunctionNode(Action Function, List<NumericNode<T>> arguments)
|
||||||
{
|
{
|
||||||
this.func = Function;
|
func = Function;
|
||||||
args = arguments;
|
args = arguments;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using MathEngine.Parser.Parser.Nodes;
|
using MathEngine.Tokenizer;
|
||||||
using MathEngine.Parser.Tokeniser;
|
|
||||||
namespace MathEngine.Parser.Parser
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
|
||||||
|
namespace MathEngine.AST.Nodes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Factory that creates a TreeNode
|
/// Factory that creates a TreeNode
|
||||||
@@ -18,17 +20,18 @@ namespace MathEngine.Parser.Parser
|
|||||||
/// <returns>A TreeNode with CurrentToken as the root value and LeftBranch and RightBranch as Children</returns>
|
/// <returns>A TreeNode with CurrentToken as the root value and LeftBranch and RightBranch as Children</returns>
|
||||||
public static BaseNode CreateBinaryNode(Token CurrentToken, BaseNode LeftBranch, BaseNode RightBranch)
|
public static BaseNode CreateBinaryNode(Token CurrentToken, BaseNode LeftBranch, BaseNode RightBranch)
|
||||||
{
|
{
|
||||||
switch (CurrentToken.Token_Type)
|
switch (CurrentToken.Type)
|
||||||
{
|
{
|
||||||
case Token.Type.Addition:
|
case TokenType.Addition:
|
||||||
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a + b);
|
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a + b);
|
||||||
case Token.Type.Subtraction:
|
case TokenType.Subtraction:
|
||||||
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a - b);
|
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a - b);
|
||||||
case Token.Type.Multiplication:
|
case TokenType.Multiplication:
|
||||||
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a * b);
|
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a * b);
|
||||||
case Token.Type.Division:
|
case TokenType.Division:
|
||||||
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a / b);
|
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a / b);
|
||||||
case Token.Type.Exponentiation:
|
case TokenType.Exponentiation:
|
||||||
|
throw new NotImplementedException("Exponentiation is not supported at this time!");
|
||||||
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a ^ b);
|
return new BinaryNode(LeftBranch, RightBranch, (a, b) => a ^ b);
|
||||||
default:
|
default:
|
||||||
throw new NotImplementedException("Attempted to create a BinaryNode with an invalid operation!");
|
throw new NotImplementedException("Attempted to create a BinaryNode with an invalid operation!");
|
||||||
@@ -63,23 +66,23 @@ namespace MathEngine.Parser.Parser
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns a Node that holds a numerical value
|
/// Creates a Function Node, that is a node that operates a number of arguments
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="CurrentToken">The token that holds the numeric value</param>
|
/// <param name="FunctionToken">The function to create the node for</param>
|
||||||
|
/// <param name="ArgumentStack">Reference to Stack of BaseNode objects to take the arguments from</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static BaseNode CreateNumericNode(Token CurrentToken)
|
public static BaseNode CreateNumericNode(Token NumericToken)
|
||||||
{
|
{
|
||||||
switch (CurrentToken.NumericalType)
|
if (NumericToken.Type != TokenType.Numeric)
|
||||||
{
|
{
|
||||||
case Token.NumericType.Integer:
|
throw new ArgumentException("Token is not of a numeric type");
|
||||||
throw new NotImplementedException("Integer Numbers are not implemented at this time");
|
}
|
||||||
case Token.NumericType.Decimal:
|
if (!(decimal.TryParse(NumericToken.Value, out decimal res)))
|
||||||
return new NumericNode<decimal>(Decimal.Parse(CurrentToken.TokenValue));
|
{
|
||||||
case Token.NumericType.Complex:
|
throw new ArgumentException("Failure to parse number");
|
||||||
throw new NotImplementedException("Complex Numbers are not implemented at this time");
|
}
|
||||||
default:
|
return new NumericNode<decimal>(res);
|
||||||
throw new InvalidDataException("Attempted to create a NumericNode with non numeric data!");
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
namespace MathEngine.Parser.Parser.Nodes
|
namespace MathEngine.AST.Nodes
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents a Tree node that can store a numeric value
|
/// Represents a Tree node that can store a numeric value
|
||||||
@@ -15,8 +15,8 @@ namespace MathEngine.Parser.Parser.Nodes
|
|||||||
/// <param name="value">The token for the nodes value</param>
|
/// <param name="value">The token for the nodes value</param>
|
||||||
public NumericNode(decimal value)
|
public NumericNode(decimal value)
|
||||||
{
|
{
|
||||||
this.Children = null;
|
Children = null;
|
||||||
this.Value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override BaseNode Add(BaseNode otherNode)
|
protected override BaseNode Add(BaseNode otherNode)
|
||||||
@@ -64,7 +64,7 @@ namespace MathEngine.Parser.Parser.Nodes
|
|||||||
if (otherNode is NumericNode<decimal>)
|
if (otherNode is NumericNode<decimal>)
|
||||||
{
|
{
|
||||||
NumericNode<decimal> rhs = (NumericNode<decimal>)otherNode;
|
NumericNode<decimal> rhs = (NumericNode<decimal>)otherNode;
|
||||||
return new NumericNode<decimal>((decimal)(Math.Pow((double)Value, (double)(rhs.Value))));
|
return new NumericNode<decimal>((decimal)Math.Pow((double)Value, (double)rhs.Value));
|
||||||
}
|
}
|
||||||
throw new InvalidOperationException("Attempted Invalid operation");
|
throw new InvalidOperationException("Attempted Invalid operation");
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
using MathEngine.Parser.Tokeniser;
|
using MathEngine.AST.Nodes;
|
||||||
|
using MathEngine.Tokenizer;
|
||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
|
||||||
namespace MathEngine.Parser.Parser
|
namespace MathEngine.AST
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class for converting from RPN form to an expression tree
|
/// Class for converting from RPN form to an expression tree
|
||||||
@@ -12,39 +14,39 @@ namespace MathEngine.Parser.Parser
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="rpnExpression">RPN expression stack to generate an expression tree from</param>
|
/// <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>
|
/// <returns>An expression Tree that represents the Mathematical expression given by rpnExpression</returns>
|
||||||
public static BaseNode TreeFromRPN(Stack<Token> rpnExpression)
|
public static BaseNode TreeFromRPN(Queue<Token> rpnExpression)
|
||||||
{
|
{
|
||||||
Stack<BaseNode> OutputStack = new(rpnExpression.Count);
|
Stack<BaseNode> OutputStack = new(rpnExpression.Count);
|
||||||
BaseNode Node;
|
BaseNode Node;
|
||||||
Token CurrentToken;
|
Token CurrentToken;
|
||||||
while (rpnExpression.Count != 0)
|
while (rpnExpression.Count != 0)
|
||||||
{
|
{
|
||||||
CurrentToken = rpnExpression.Pop();
|
CurrentToken = rpnExpression.Dequeue();
|
||||||
switch (CurrentToken.Token_Type)
|
switch (CurrentToken.Type)
|
||||||
{
|
{
|
||||||
case Token.Type.Numeric:
|
case TokenType.Numeric:
|
||||||
Node = NodeFactory.CreateNumericNode(CurrentToken);
|
Node = NodeFactory.CreateNumericNode(CurrentToken);
|
||||||
OutputStack.Push(Node);
|
OutputStack.Push(Node);
|
||||||
break;
|
break;
|
||||||
// We need to preserve "Left handness" of the ExpressionTree
|
// We need to preserve "Left handness" of the ExpressionTree
|
||||||
// i.e 7/8 gives a root node of / with Cnode(0) = 7 and Cnod(1) = 8 etc.
|
// i.e 7/8 gives a root node of / with Cnode(0) = 7 and Cnod(1) = 8 etc.
|
||||||
// This should preserve non commutativity
|
// This should preserve non commutativity
|
||||||
case Token.Type.Addition:
|
case TokenType.Addition:
|
||||||
case Token.Type.Subtraction:
|
case TokenType.Subtraction:
|
||||||
case Token.Type.Multiplication:
|
case TokenType.Multiplication:
|
||||||
case Token.Type.Division:
|
case TokenType.Division:
|
||||||
case Token.Type.Exponentiation:
|
case TokenType.Exponentiation:
|
||||||
BaseNode Right = OutputStack.Pop();
|
BaseNode Right = OutputStack.Pop();
|
||||||
BaseNode Left = OutputStack.Pop();
|
BaseNode Left = OutputStack.Pop();
|
||||||
Node = NodeFactory.CreateBinaryNode(CurrentToken, Left, Right);
|
Node = NodeFactory.CreateBinaryNode(CurrentToken, Left, Right);
|
||||||
OutputStack.Push(Node);
|
OutputStack.Push(Node);
|
||||||
break;
|
break;
|
||||||
case Token.Type.UnaryPlus:
|
case TokenType.UnaryPlus:
|
||||||
case Token.Type.UnaryMinus:
|
case TokenType.UnaryMinus:
|
||||||
//Node = NodeFactory.CreateUnaryNode(CurrentToken, OutputStack.Pop());
|
//Node = NodeFactory.CreateUnaryNode(CurrentToken, OutputStack.Pop());
|
||||||
//OutputStack.Push(Node);
|
//OutputStack.Push(Node);
|
||||||
break;
|
break;
|
||||||
case Token.Type.Function:
|
case TokenType.Function:
|
||||||
Node = NodeFactory.CreateFunctionNode(CurrentToken, OutputStack);
|
Node = NodeFactory.CreateFunctionNode(CurrentToken, OutputStack);
|
||||||
OutputStack.Push(Node);
|
OutputStack.Push(Node);
|
||||||
break;
|
break;
|
||||||
4
MathEngine/MathEngine/AssemblyInfo.cs
Normal file
4
MathEngine/MathEngine/AssemblyInfo.cs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
// AssemblyInfo.cs (or any C# source file in your main project)
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
[assembly: InternalsVisibleTo("EngineTests")]
|
||||||
35
MathEngine/MathEngine/Expression/Expression.cs
Normal file
35
MathEngine/MathEngine/Expression/Expression.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using MathEngine.AST;
|
||||||
|
using MathEngine.AST.Nodes;
|
||||||
|
|
||||||
|
namespace MathEngine.Expression
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a Mathematical expression.
|
||||||
|
/// </summary>
|
||||||
|
public class Expression
|
||||||
|
{
|
||||||
|
|
||||||
|
private ExpressionTree expressionTree;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialises a new instance of the
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expression">String representing the Mathematical expression</param>
|
||||||
|
public Expression(string expression)
|
||||||
|
{
|
||||||
|
expressionTree = new ExpressionTree(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Expression(ExpressionTree tree)
|
||||||
|
{
|
||||||
|
expressionTree = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Expression Evaluate()
|
||||||
|
{
|
||||||
|
return new Expression(expressionTree.Evaluate());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,20 +1,24 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net7.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Remove="Parser\Nodes\NumericIntegerNode.cs" />
|
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||||
<Compile Remove="Parser\Nodes\TreeNode.cs" />
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</ItemGroup>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
<ItemGroup>
|
</PackageReference>
|
||||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
<_Parameter1>EngineTests</_Parameter1>
|
<PackageReference Include="Microsoft.TestPlatform.TestHost" Version="17.14.1" />
|
||||||
</AssemblyAttribute>
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.2">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,90 +0,0 @@
|
|||||||
using MathEngine.Parser.Parser;
|
|
||||||
using MathEngine.Parser.Parser.Nodes;
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace MathEngine.Parser.Nodes
|
|
||||||
{
|
|
||||||
internal class NumericIntegerNode: BaseNode
|
|
||||||
{
|
|
||||||
private readonly decimal Value;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initialises a new instance of a NumericNode with a given Token
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="value">The token for the nodes value</param>
|
|
||||||
public NumericIntegerNode(decimal value)
|
|
||||||
{
|
|
||||||
this.Children = null;
|
|
||||||
this.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override BaseNode Add(BaseNode otherNode)
|
|
||||||
{
|
|
||||||
if (otherNode is NumericIntegerNode)
|
|
||||||
{
|
|
||||||
NumericIntegerNode rhs = (NumericIntegerNode)otherNode;
|
|
||||||
return new NumericIntegerNode(Value + rhs.Value);
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("Attempted Invalid operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override BaseNode Subtract(BaseNode otherNode)
|
|
||||||
{
|
|
||||||
if (otherNode is NumericIntegerNode)
|
|
||||||
{
|
|
||||||
NumericIntegerNode rhs = (NumericIntegerNode)otherNode;
|
|
||||||
return new NumericIntegerNode(Value - rhs.Value);
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("Attempted Invalid operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override BaseNode Multiply(BaseNode otherNode)
|
|
||||||
{
|
|
||||||
if (otherNode is NumericIntegerNode)
|
|
||||||
{
|
|
||||||
NumericIntegerNode rhs = (NumericIntegerNode)otherNode;
|
|
||||||
return new NumericIntegerNode(Value * rhs.Value);
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("Attempted Invalid operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override BaseNode Divide(BaseNode otherNode)
|
|
||||||
{
|
|
||||||
if (otherNode is NumericIntegerNode)
|
|
||||||
{
|
|
||||||
NumericIntegerNode rhs = (NumericIntegerNode)otherNode;
|
|
||||||
return new NumericIntegerNode(Value / rhs.Value);
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("Attempted Invalid operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override BaseNode Exponentiate(BaseNode otherNode)
|
|
||||||
{
|
|
||||||
if (otherNode is NumericIntegerNode)
|
|
||||||
{
|
|
||||||
NumericIntegerNode rhs = (NumericIntegerNode)otherNode;
|
|
||||||
return new NumericIntegerNode((Int64)(Math.Pow((double)(Value), (double)rhs.Value)));
|
|
||||||
}
|
|
||||||
throw new InvalidOperationException("Attempted Invalid operation");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Evaluates the Numeric Node by simply returning the current instance
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
public override BaseNode Evaluate()
|
|
||||||
{
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Value.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
218
MathEngine/MathEngine/Parser/Parser.cs
Normal file
218
MathEngine/MathEngine/Parser/Parser.cs
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
using MathEngine.Tokenizer;
|
||||||
|
using System.Security;
|
||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
|
||||||
|
namespace MathEngine.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>An integer value that represent the precedence value of the token</returns>
|
||||||
|
private static int OperatorPrecedence(Token X)
|
||||||
|
{
|
||||||
|
switch (X.Type)
|
||||||
|
{
|
||||||
|
case TokenType.Addition:
|
||||||
|
case TokenType.Subtraction:
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
case TokenType.Multiplication:
|
||||||
|
case TokenType.Division:
|
||||||
|
{
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
case TokenType.UnaryPlus:
|
||||||
|
case TokenType.UnaryMinus:
|
||||||
|
{
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
case TokenType.Exponentiation:
|
||||||
|
{
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
case TokenType.LeftParenthesis:
|
||||||
|
case TokenType.RightParenthesis:
|
||||||
|
{
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
throw new Exception("Unknown operator precedence" + X.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is the operation left associative
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="X">Operation to check</param>
|
||||||
|
/// <returns>True if the token is left associative, False otherwise</returns>
|
||||||
|
private static bool IsLeftAssociatve(Token X)
|
||||||
|
{
|
||||||
|
switch (X.Type)
|
||||||
|
{
|
||||||
|
case TokenType.Addition:
|
||||||
|
case TokenType.Subtraction:
|
||||||
|
case TokenType.Multiplication:
|
||||||
|
case TokenType.Division:
|
||||||
|
case TokenType.LeftParenthesis:
|
||||||
|
case TokenType.RightParenthesis:
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case TokenType.Exponentiation:
|
||||||
|
case TokenType.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>
|
||||||
|
/// Pops operators from the operator stack to the output queue based on precedence and associativity rules.
|
||||||
|
/// This method is part of the Shunting Yard algorithm, ensuring that operators with higher or equal precedence
|
||||||
|
/// (when left-associative) are correctly ordered in the resulting Reverse Polish Notation (RPN).
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="operatorStack">The stack holding operators and functions that are yet to be added to the output.</param>
|
||||||
|
/// <param name="outputQueue">The queue that accumulates tokens in RPN order.</param>
|
||||||
|
/// <param name="current">The current operator token being processed from the input expression.</param>
|
||||||
|
private static void PopOperatorsToQueue(Stack<Token> operatorStack, Queue<Token> outputQueue, Token current)
|
||||||
|
{
|
||||||
|
while (operatorStack.Count > 0)
|
||||||
|
{
|
||||||
|
Token top = operatorStack.Peek();
|
||||||
|
bool isFunction = top.Type == TokenType.Function;
|
||||||
|
bool isHigherPrecedence = OperatorPrecedence(top) > OperatorPrecedence(current);
|
||||||
|
bool isEqualPrecedenceAndLeftAssociative = ((OperatorPrecedence(top) == OperatorPrecedence(current)) && IsLeftAssociatve(current));
|
||||||
|
bool isNotLeftParenthesis = top.Type != TokenType.LeftParenthesis;
|
||||||
|
|
||||||
|
if ((isFunction || isHigherPrecedence || isEqualPrecedenceAndLeftAssociative) && isNotLeftParenthesis)
|
||||||
|
{
|
||||||
|
outputQueue.Enqueue(operatorStack.Pop());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles pushing either an addition or subtraction operator token onto the operator stack, distinguishing
|
||||||
|
/// between unary and binary addition or subtract operators based on the previous token context.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="operatorStack">The stack holding operators during parsing.</param>
|
||||||
|
/// <param name="outputQueue">The output queue representing the RPN expression.</param>
|
||||||
|
/// <param name="current">The current operator token being processed.</param>
|
||||||
|
/// <param name="previous">The previous token processed, used to determine if the current operator is unary.</param>
|
||||||
|
private static void PushAdditionSubtractionWithUnaryCheck(Stack<Token> operatorStack, Queue<Token> outputQueue, Token current, Token previous)
|
||||||
|
{
|
||||||
|
bool isUnary = previous.Type == TokenType.None ||
|
||||||
|
previous.Type == TokenType.LeftParenthesis ||
|
||||||
|
previous.Type == TokenType.Addition ||
|
||||||
|
previous.Type == TokenType.Subtraction ||
|
||||||
|
previous.Type == TokenType.Multiplication ||
|
||||||
|
previous.Type == TokenType.Division ||
|
||||||
|
previous.Type == TokenType.Exponentiation;
|
||||||
|
|
||||||
|
if (isUnary)
|
||||||
|
{
|
||||||
|
var unaryToken = current.Type == TokenType.Subtraction ? Token.UnaryMinus : Token.UnaryPlus;
|
||||||
|
PopOperatorsToQueue(operatorStack, outputQueue, unaryToken);
|
||||||
|
operatorStack.Push(unaryToken);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PopOperatorsToQueue(operatorStack, outputQueue, current);
|
||||||
|
operatorStack.Push(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses a list of tokens into a valid RPN expression
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Expression">List of tokens to parse</param>
|
||||||
|
/// <returns>Returns a <see cref="Queue{Token}"/> representing the Reverse polish notation form of the expression</returns>
|
||||||
|
internal static Queue<Token> Parse(List<Token> Expression)
|
||||||
|
{
|
||||||
|
//Temp holding stack for operators
|
||||||
|
Stack<Token> OperatorStack = new(Expression.Count/2);
|
||||||
|
//The final stack to return
|
||||||
|
Queue<Token> rpnQueue = new(Expression.Count);
|
||||||
|
//Stack used to hold the number of input params to a function
|
||||||
|
//Stack<uint> ArityStack = new Stack<uint>();
|
||||||
|
Token PreviousToken = Token.None;
|
||||||
|
Token CurrentToken;
|
||||||
|
|
||||||
|
for (int i = 0; i < Expression.Count; i++)
|
||||||
|
{
|
||||||
|
CurrentToken = Expression[i];
|
||||||
|
switch (CurrentToken.Type)
|
||||||
|
{
|
||||||
|
case TokenType.Numeric:
|
||||||
|
rpnQueue.Enqueue(CurrentToken);
|
||||||
|
break;
|
||||||
|
case TokenType.Addition:
|
||||||
|
case TokenType.Subtraction:
|
||||||
|
PushAdditionSubtractionWithUnaryCheck(OperatorStack, rpnQueue, CurrentToken, PreviousToken);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TokenType.Multiplication:
|
||||||
|
case TokenType.Division:
|
||||||
|
case TokenType.Exponentiation:
|
||||||
|
PopOperatorsToQueue(OperatorStack, rpnQueue, CurrentToken);
|
||||||
|
OperatorStack.Push(CurrentToken);
|
||||||
|
break;
|
||||||
|
case TokenType.Function:
|
||||||
|
OperatorStack.Push(CurrentToken);
|
||||||
|
break;
|
||||||
|
case TokenType.LeftParenthesis:
|
||||||
|
OperatorStack.Push(CurrentToken);
|
||||||
|
break;
|
||||||
|
case TokenType.RightParenthesis:
|
||||||
|
while (OperatorStack.Count != 0 && (OperatorStack.Peek() != Token.LeftParenthesis))
|
||||||
|
{
|
||||||
|
rpnQueue.Enqueue(OperatorStack.Pop());
|
||||||
|
}
|
||||||
|
if (OperatorStack.Count == 0)
|
||||||
|
{
|
||||||
|
throw new ParserException("Mismatched parentheses: Missing left parenthesis");
|
||||||
|
}
|
||||||
|
OperatorStack.Pop(); // Discard the left parenthesis
|
||||||
|
|
||||||
|
if (OperatorStack.Count > 0 && (OperatorStack.Peek().Type == TokenType.Function))
|
||||||
|
{
|
||||||
|
rpnQueue.Enqueue(OperatorStack.Pop());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
PreviousToken = CurrentToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
while ((OperatorStack.Count > 0))
|
||||||
|
{
|
||||||
|
if (OperatorStack.Peek().Type == TokenType.LeftParenthesis || OperatorStack.Peek().Type == TokenType.RightParenthesis)
|
||||||
|
throw new Exception("Mismatched parentheses; Expected (");
|
||||||
|
else
|
||||||
|
rpnQueue.Enqueue(OperatorStack.Pop());
|
||||||
|
}
|
||||||
|
return rpnQueue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
using MathEngine.Parser.Tokeniser;
|
|
||||||
namespace MathEngine.Parser.Parser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents an Abstract Syntax tree for expresison evaluation
|
|
||||||
/// </summary>
|
|
||||||
public class ExpressionTree
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The root node of the expression tree;
|
|
||||||
/// </summary>
|
|
||||||
private readonly BaseNode rootNode;
|
|
||||||
private readonly BaseNode? evaluated_expression;
|
|
||||||
|
|
||||||
/// <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">String representing the </param>
|
|
||||||
public ExpressionTree(string Expression)
|
|
||||||
{
|
|
||||||
List<Token> tokens = Tokeniser.Tokeniser.Tokenise(Expression);
|
|
||||||
Stack<Token> rpnForm = Parser.Parse(tokens);
|
|
||||||
rootNode = TreeGenerator.TreeFromRPN(rpnForm);
|
|
||||||
evaluated_expression = rootNode.Evaluate();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ExpressionTree(BaseNode rootNode)
|
|
||||||
{
|
|
||||||
this.rootNode = rootNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* /// <summary>
|
|
||||||
/// Evaluates the current instance of ExpressionTree
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>Returns an update of the current instance which the expression Evaluated</returns>
|
|
||||||
public ExpressionTree Evaluate()
|
|
||||||
{
|
|
||||||
return new ExpressionTree(rootNode.Evaluate());
|
|
||||||
}*/
|
|
||||||
|
|
||||||
/// <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>True if they are equal, False otherwise</returns>
|
|
||||||
//public override bool Equals(object? other)
|
|
||||||
//{
|
|
||||||
// if (other is BaseNode)
|
|
||||||
// {
|
|
||||||
// ExpressionTree otherTree = new((BaseNode)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(rootNode, evaluated_expression);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return evaluated_expression.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
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>An integer value that represent the precedence value of the token</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>True if the token is left associative, False otherwise</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>The input stack in the reserve order</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>
|
|
||||||
internal static 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:
|
|
||||||
case Token.Type.Exponentiation:
|
|
||||||
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;
|
|
||||||
case Token.Type.Function:
|
|
||||||
OperatorStack.Push(CurrentToken);
|
|
||||||
break;
|
|
||||||
case Token.Type.LeftParenthesis:
|
|
||||||
OperatorStack.Push(CurrentToken);
|
|
||||||
break;
|
|
||||||
case Token.Type.RightParenthesis:
|
|
||||||
while (OperatorStack.Peek() != Token.LeftParenthesis && OperatorStack.Count != 0)
|
|
||||||
{
|
|
||||||
OutputStack.Push(OperatorStack.Pop());
|
|
||||||
}
|
|
||||||
OperatorStack.Pop(); // Discard the left parenthesis
|
|
||||||
|
|
||||||
if (OperatorStack.Peek().Token_Type == Token.Type.Function)
|
|
||||||
{
|
|
||||||
OutputStack.Push(OperatorStack.Pop());
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
37
MathEngine/MathEngine/Parser/ParserException.cs
Normal file
37
MathEngine/MathEngine/Parser/ParserException.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
namespace MathEngine.Parser
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an error that occured during the Parsing process
|
||||||
|
/// </summary>
|
||||||
|
internal class ParserException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes A new instance of the <see cref="TokenizerException"></see> class
|
||||||
|
/// </summary>
|
||||||
|
public ParserException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes A new instance of the <see cref="TokenizerException"></see> class with a specified error message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error</param>
|
||||||
|
public ParserException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes A new instance of the <see cref="TokenizerException"></see> class with a specified error message and a reference to the inner exception that is the cause of this exception
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error</param>
|
||||||
|
/// <param name="innerException">The exception that is the cause of the current excepton</param>
|
||||||
|
public ParserException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
namespace MathEngine.Parser.Tokeniser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Defines the Token Type. The base for all manipulations
|
|
||||||
/// </summary>
|
|
||||||
internal readonly 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>
|
|
||||||
/// Represents the token for (
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Token LeftParenthesis = new Token("(", Token.Type.LeftParenthesis, Token.NumericType.NaN, 0);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the token for )
|
|
||||||
/// </summary>
|
|
||||||
public readonly static Token RightParenthesis = new Token(")", Token.Type.RightParenthesis, Token.NumericType.NaN, 0);
|
|
||||||
|
|
||||||
/// Represents the token for ^
|
|
||||||
/// </summary>
|
|
||||||
public static readonly Token Exponentiation = new("^", Type.Exponentiation, 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the Tokeniser.Token structure with a given TokenValue, TokenType, TokenNumericType and Arity
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="TokenValue">Char 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(char TokenValue, Type TokenType, NumericType TokenNumericType, uint FunctionArity = 0)
|
|
||||||
{
|
|
||||||
this.Value = TokenValue.ToString();
|
|
||||||
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 a string representation of the current token instance</returns>
|
|
||||||
public new string ToString()
|
|
||||||
{
|
|
||||||
return Value + "," + TokenType.ToString() + "," + Numeric_Type.ToString() + "," + Arity.ToString();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a value that indicates whether 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 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 true;
|
|
||||||
}
|
|
||||||
if (X.TokenType != Y.TokenType)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (X.NumericalType != Y.NumericalType)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (X.Arity != Y.Arity)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a value that indicates whether the given Token is equal to the current Token instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="other">Token to compare</param>
|
|
||||||
/// <returns>Returns true if the two Tokens are equal and false otherwise</returns>
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns a value that indicates whether an object is equal to the current Token instance.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="obj">The object to compare to the Token instance</param>
|
|
||||||
/// <returns>Returns true if the object is equal to the Token instance, false otherwise</returns>
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if (obj is Token other)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,136 +0,0 @@
|
|||||||
namespace MathEngine.Parser.Tokeniser
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the conversion of a Mathematical expression in string form to a List of Tokens
|
|
||||||
/// </summary>
|
|
||||||
internal static class Tokeniser
|
|
||||||
{
|
|
||||||
private static readonly List<char> Operators = new(new char[] { '+', '-', '*', '/', '^' });
|
|
||||||
private static readonly List<char> Parenthesis = 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;
|
|
||||||
currentIndex++;
|
|
||||||
curChar = currentIndex >= Expresison.Length ? '\0' : Expresison[currentIndex];
|
|
||||||
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,
|
|
||||||
'^' => Token.Exponentiation,
|
|
||||||
_ => throw new Exception(String.Format("Character {0} is not a defined operator", curChar)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
static private Token GetParenthesisToken(char curChar)
|
|
||||||
{
|
|
||||||
return curChar switch
|
|
||||||
{
|
|
||||||
'(' => Token.LeftParenthesis,
|
|
||||||
')' => Token.RightParenthesis,
|
|
||||||
_ => throw new Exception(String.Format("Character {0} is not Parenthesis", 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;
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
if (Parenthesis.Contains(curChar))
|
|
||||||
{
|
|
||||||
Tokenstack.Add(Tokeniser.GetParenthesisToken(curChar));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//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
|
|
||||||
}
|
|
||||||
else if (Char.IsAsciiLetter(curChar)) // Keep reading until a parenthesis
|
|
||||||
{
|
|
||||||
Int32 tempIndex = currentIndex;
|
|
||||||
char tempChar;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
tempChar = GetNextChar(Expression, ref tempIndex);
|
|
||||||
|
|
||||||
} while (Char.IsAscii(tempChar) && tempChar != '(');
|
|
||||||
//Token is a potential function so add this to the stack
|
|
||||||
Tokenstack.Add(new Token(Expression[currentIndex..tempIndex], Token.Type.Function, Token.NumericType.NaN, 0));
|
|
||||||
currentIndex = tempIndex - 1; //Sets the index variable to just before the end of the function
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//return the stack after triming
|
|
||||||
Tokenstack.TrimExcess();
|
|
||||||
|
|
||||||
return Tokenstack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
144
MathEngine/MathEngine/Tokeniser/Token.cs
Normal file
144
MathEngine/MathEngine/Tokeniser/Token.cs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
|
||||||
|
namespace MathEngine.Tokenizer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the Token type. The base for all manipulations.
|
||||||
|
/// </summary>
|
||||||
|
internal readonly record struct Token(
|
||||||
|
string Value,
|
||||||
|
TokenType Type,
|
||||||
|
uint Arity = 0
|
||||||
|
)
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Enum representing the token type.
|
||||||
|
/// </summary>
|
||||||
|
internal enum TokenType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
|
||||||
|
Numeric,
|
||||||
|
DecimalPoint,
|
||||||
|
Addition,
|
||||||
|
Subtraction,
|
||||||
|
Multiplication,
|
||||||
|
Division,
|
||||||
|
Exponentiation,
|
||||||
|
UnaryPlus,
|
||||||
|
UnaryMinus,
|
||||||
|
Operator,
|
||||||
|
Variable,
|
||||||
|
Function,
|
||||||
|
FunctionArgumentSeparator,
|
||||||
|
LeftParenthesis,
|
||||||
|
RightParenthesis,
|
||||||
|
OpenBracket,
|
||||||
|
CloseBracket,
|
||||||
|
OpenBrace,
|
||||||
|
CloseBrace
|
||||||
|
}
|
||||||
|
|
||||||
|
// Predefined tokens
|
||||||
|
public static readonly Token None = new("", TokenType.None);
|
||||||
|
public static readonly Token Plus = new("+", TokenType.Addition);
|
||||||
|
public static readonly Token UnaryPlus = new("-", TokenType.UnaryPlus);
|
||||||
|
public static readonly Token Minus = new("-", TokenType.Subtraction);
|
||||||
|
public static readonly Token UnaryMinus = new("-", TokenType.UnaryMinus);
|
||||||
|
public static readonly Token Multiply = new("*", TokenType.Multiplication);
|
||||||
|
public static readonly Token Divide = new("/", TokenType.Division);
|
||||||
|
public static readonly Token Exponentiation = new("^", TokenType.Exponentiation);
|
||||||
|
public static readonly Token LeftParenthesis = new("(", TokenType.LeftParenthesis);
|
||||||
|
public static readonly Token RightParenthesis = new(")", TokenType.RightParenthesis);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the token represents any arithmetic operator.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsOperator
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Type is TokenType.Addition
|
||||||
|
or TokenType.Subtraction
|
||||||
|
or TokenType.Multiplication
|
||||||
|
or TokenType.Division
|
||||||
|
or TokenType.Exponentiation
|
||||||
|
or TokenType.UnaryPlus
|
||||||
|
or TokenType.UnaryMinus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the token represents a numeric literal.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsNumeric
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Type is TokenType.Numeric;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the token is a variable
|
||||||
|
/// </summary>
|
||||||
|
public bool IsVariable
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Type is TokenType.Variable;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the token represents a function
|
||||||
|
/// </summary>
|
||||||
|
public bool IsFunction
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Type is TokenType.Function;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the token is a left or right parenthesis.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsParenthesis
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Type is TokenType.LeftParenthesis or TokenType.RightParenthesis;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the token is a grouping symbol of any kind.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsGrouping
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Type is TokenType.LeftParenthesis
|
||||||
|
or TokenType.RightParenthesis
|
||||||
|
or TokenType.OpenBracket
|
||||||
|
or TokenType.CloseBracket
|
||||||
|
or TokenType.OpenBrace
|
||||||
|
or TokenType.CloseBrace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns true if the token separates function arguments.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsArgumentSeparator
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Type is TokenType.FunctionArgumentSeparator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
MathEngine/MathEngine/Tokeniser/Tokeniser.cs
Normal file
172
MathEngine/MathEngine/Tokeniser/Tokeniser.cs
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
|
||||||
|
using MathEngine.Tokenizer;
|
||||||
|
using static MathEngine.Tokenizer.Token;
|
||||||
|
|
||||||
|
namespace MathEngine.Tokenizer
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the conversion of a Mathematical expression in string form to a List of Tokens
|
||||||
|
/// </summary>
|
||||||
|
internal static class ExpressionTokenizer
|
||||||
|
{
|
||||||
|
private static readonly HashSet<char> Operators = new() { '+', '-', '*', '/', '^' };
|
||||||
|
private static readonly HashSet<char> Parenthesis = new() { '(', ')' };
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a character represents an ASCII numerical digit
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">The character to check</param>
|
||||||
|
/// <returns>True if <paramref name="c"/> represents an ASCII numerical digit, false otherwise</returns>
|
||||||
|
private static bool IsAsciiDigit(char c)
|
||||||
|
{
|
||||||
|
return c >= '0' && c <= '9';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a character represents an ASCII whitespace character
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">The character to check</param>
|
||||||
|
/// <returns>True if <paramref name="c"/> represents an ASCII whitespace character, false otherwise</returns>
|
||||||
|
private static bool IsWhitespace(char c)
|
||||||
|
{
|
||||||
|
return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\v' || c == '\f';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a character represents an ASCII dot character
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="c">The character to check</param>
|
||||||
|
/// <returns>True if <paramref name="c"/> represents an ASCII dot character, false otherwise</returns>
|
||||||
|
private static bool IsDecimalPoint(char c)
|
||||||
|
{
|
||||||
|
return c == '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the <paramref name="expression"/> to read a numeric <typeparamref name="Token"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="expression">The expression being parsed</param>
|
||||||
|
/// <param name="at_decimal_point">Indicates if the character read before calling this method was a decimal point or not</param>
|
||||||
|
/// <param name="current_index">The current index the parser is at</param>
|
||||||
|
/// <returns>A <typeparamref name="Token"/> representing the numeric value read</returns>
|
||||||
|
private static Token ReadNumericToken(ReadOnlySpan<char> expression, bool at_decimal_point, ref Int32 currentIndex)
|
||||||
|
{
|
||||||
|
Int32 tempIndex = currentIndex + 1;
|
||||||
|
bool hasDecimalPlace = at_decimal_point;
|
||||||
|
char tempChar;
|
||||||
|
|
||||||
|
while (tempIndex < expression.Length)
|
||||||
|
{
|
||||||
|
tempChar = expression[tempIndex];
|
||||||
|
if (tempChar == '.' && !hasDecimalPlace)
|
||||||
|
{
|
||||||
|
hasDecimalPlace = true;
|
||||||
|
}
|
||||||
|
else if (tempChar == '.' && hasDecimalPlace)
|
||||||
|
{
|
||||||
|
int errIndex = tempIndex + 1;
|
||||||
|
while (errIndex < expression.Length && (IsAsciiDigit(tempChar) || IsDecimalPoint(tempChar)))
|
||||||
|
{
|
||||||
|
errIndex++;
|
||||||
|
}
|
||||||
|
ReadOnlySpan<char> errSpan = expression.Slice(currentIndex, errIndex - currentIndex);
|
||||||
|
throw new TokenizerException($"Syntax error: The number {errSpan.ToString()} has multiple decimal point when at most one is allowed.");
|
||||||
|
}
|
||||||
|
else if (!IsAsciiDigit(tempChar) && tempChar != '.')
|
||||||
|
{
|
||||||
|
break; // end of numeric token
|
||||||
|
}
|
||||||
|
|
||||||
|
tempIndex++;
|
||||||
|
}
|
||||||
|
ReadOnlySpan<char> numberSpan = expression.Slice(currentIndex, tempIndex - currentIndex);
|
||||||
|
currentIndex = tempIndex - 1; //Sets the index variable to just before the non numeric char
|
||||||
|
//Token is a number so add this to the stack
|
||||||
|
return new Token(numberSpan.ToString(), TokenType.Numeric);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static private Token GetOperatorToken(char curChar)
|
||||||
|
{
|
||||||
|
return curChar switch
|
||||||
|
{
|
||||||
|
'+' => Token.Plus,
|
||||||
|
'-' => Token.Minus,
|
||||||
|
'*' => Token.Multiply,
|
||||||
|
'/' => Token.Divide,
|
||||||
|
'^' => Token.Exponentiation,
|
||||||
|
_ => throw new TokenizerException($"Recieved unknown character '{curChar}' when attempting to parse an operator.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static private Token GetParenthesisToken(char curChar)
|
||||||
|
{
|
||||||
|
return curChar switch
|
||||||
|
{
|
||||||
|
'(' => Token.LeftParenthesis,
|
||||||
|
')' => Token.RightParenthesis,
|
||||||
|
_ => throw new TokenizerException($"Recieved unknown character '{curChar}' when attempting to parse a parenthesis."),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tokenizes 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> Tokenize(ReadOnlySpan<char> expression)
|
||||||
|
{
|
||||||
|
if (expression.IsEmpty)
|
||||||
|
{
|
||||||
|
return new List<Token> { };
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Token> Tokenstack = new(expression.Length); //Nearly always is overallocated to the true number of tokens but avoids the need to keep reallocating for a growing stack
|
||||||
|
char curChar;
|
||||||
|
for (int i = 0; i < expression.Length; i++)
|
||||||
|
{
|
||||||
|
curChar = expression[i];
|
||||||
|
|
||||||
|
//Cleanup whitespace
|
||||||
|
if (IsWhitespace(curChar))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Switch on special characters
|
||||||
|
if (Operators.Contains(curChar))
|
||||||
|
{
|
||||||
|
Tokenstack.Add(GetOperatorToken(curChar));
|
||||||
|
continue; //Next loop interation
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Parenthesis.Contains(curChar))
|
||||||
|
{
|
||||||
|
Tokenstack.Add(GetParenthesisToken(curChar));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Number, two cases two consider, case 1) number is something like 142.2; case 2 .5 which is clearly 0.5.
|
||||||
|
//Case 1
|
||||||
|
if (IsAsciiDigit(curChar))
|
||||||
|
{
|
||||||
|
Tokenstack.Add(ReadNumericToken(expression, false, ref i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (IsDecimalPoint(curChar))
|
||||||
|
{
|
||||||
|
Tokenstack.Add(ReadNumericToken(expression, true, ref i));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//return the stack after triming
|
||||||
|
Tokenstack.TrimExcess();
|
||||||
|
return Tokenstack;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
MathEngine/MathEngine/Tokeniser/TokenizerException.cs
Normal file
37
MathEngine/MathEngine/Tokeniser/TokenizerException.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
namespace MathEngine.Tokenizer
|
||||||
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an error that occured during the Tokenization process
|
||||||
|
/// </summary>
|
||||||
|
internal class TokenizerException : Exception
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes A new instance of the <see cref="TokenizerException"></see> class
|
||||||
|
/// </summary>
|
||||||
|
public TokenizerException()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes A new instance of the <see cref="TokenizerException"></see> class with a specified error message
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error</param>
|
||||||
|
public TokenizerException(string message)
|
||||||
|
: base(message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes A new instance of the <see cref="TokenizerException"></see> class with a specified error message and a reference to the inner exception that is the cause of this exception
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="message">The message that describes the error</param>
|
||||||
|
/// <param name="innerException">The exception that is the cause of the current excepton</param>
|
||||||
|
public TokenizerException(string message, Exception innerException)
|
||||||
|
: base(message, innerException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
MathEngine/MathRunner/MathRunner.csproj
Normal file
21
MathEngine/MathRunner/MathRunner.csproj
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\MathEngine\MathEngine.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
15
MathEngine/MathRunner/Program.cs
Normal file
15
MathEngine/MathRunner/Program.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using MathEngine.Expression;
|
||||||
|
|
||||||
|
namespace MathRunner
|
||||||
|
{
|
||||||
|
internal class Program
|
||||||
|
{
|
||||||
|
static void Main(string[] args)
|
||||||
|
{
|
||||||
|
Console.WriteLine("Evaluting expression...");
|
||||||
|
Expression exp = new("54+2+1");
|
||||||
|
Console.WriteLine(exp.Evaluate());
|
||||||
|
Console.WriteLine("Hello, World!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user