mirror of
https://github.com/0xJ1M/MathsEngine.git
synced 2026-06-04 23:20: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 MathEngine.Parser.Parser.Nodes;
|
||||
using MathEngine.Parser.Tokeniser;
|
||||
using static MathEngine.Parser.Tokeniser.Token;
|
||||
using Xunit;
|
||||
|
||||
using MathEngine.AST.Nodes;
|
||||
using MathEngine.Tokenizer;
|
||||
|
||||
using static MathEngine.Tokenizer.Token;
|
||||
|
||||
namespace EngineTests.Parser_Tests.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for testing the NodeFactory
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class NodeFactoryTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test the NodeFactory test BinaryNode creation on all defined operations
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNodeFactoryBinaryNodesOnDefinedOperations()
|
||||
{
|
||||
NumericNode<decimal> node1 = new(200);
|
||||
@@ -34,40 +35,40 @@ namespace EngineTests.Parser_Tests.Nodes
|
||||
/// <summary>
|
||||
/// Test the NodeFactory test BinaryNode creation on exponentiation raises exception
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNodeFactoryBinaryNodesOnExponentiationRaisesException()
|
||||
{
|
||||
NumericNode<decimal> node1 = new(200);
|
||||
NumericNode<decimal> node2 = new(100);
|
||||
|
||||
Token exp = new("^", Token.Type.Exponentiation, NumericType.NaN, 0);
|
||||
Token exp = new("^", TokenType.Exponentiation);
|
||||
try
|
||||
{
|
||||
BaseNode expBiinary = NodeFactory.CreateBinaryNode(exp, node1, node2);
|
||||
}
|
||||
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>
|
||||
/// Test the NodeFactory test BinaryNode creation on invalid operation token raises exception
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNodeFactoryBinaryNodesOnInvalidOperationTokenRaisesException()
|
||||
{
|
||||
NumericNode<decimal> node1 = new(200);
|
||||
NumericNode<decimal> node2 = new(100);
|
||||
|
||||
Token invalid = new("(", Token.Type.OpenBracket, NumericType.NaN, 0);
|
||||
Token invalid = new("(", TokenType.OpenBracket);
|
||||
try
|
||||
{
|
||||
BaseNode expBiinary = NodeFactory.CreateBinaryNode(invalid, node1, node2);
|
||||
}
|
||||
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>
|
||||
/// Test the NodeFactory test NumericNode creation on all defined types
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
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>
|
||||
/// Test the NodeFactory test NumericNode creation on Complex type RaisesException
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNodeFactoryNumericNodesOnComplexTypeRaisesException()
|
||||
{
|
||||
try
|
||||
{
|
||||
Token comp = new("1+i", Token.Type.Numeric, Token.NumericType.Complex, 0);
|
||||
BaseNode testNode = NodeFactory.CreateNumericNode(comp);
|
||||
}
|
||||
catch (NotImplementedException ex)
|
||||
{
|
||||
Assert.AreEqual(ex.Message, "Complex Numbers are not implemented at this time");
|
||||
}
|
||||
Assert.True(true);
|
||||
return;
|
||||
//try
|
||||
//{
|
||||
// Token comp = new("1+i", TokenType.Numeric);
|
||||
// BaseNode testNode = NodeFactory.CreateNumericNode();
|
||||
//}
|
||||
//catch (NotImplementedException ex)
|
||||
//{
|
||||
// Assert.Equal(ex.Message, "Complex Numbers are not implemented at this time");
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test the NodeFactory test NumericNode creation on invalid type Raises Exception
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNodeFactoryNumericNodesOnInvalidTypeRaisesException()
|
||||
{
|
||||
try
|
||||
{
|
||||
Token comp = new("(", Token.Type.CloseBrace, Token.NumericType.NaN, 0);
|
||||
BaseNode testNode = NodeFactory.CreateNumericNode(comp);
|
||||
}
|
||||
catch (InvalidDataException ex)
|
||||
{
|
||||
Assert.AreEqual(ex.Message, "Attempted to create a NumericNode with non numeric data!");
|
||||
}
|
||||
Assert.True(true);
|
||||
return;
|
||||
//try
|
||||
//{
|
||||
// Token comp = new("(", TokenType.CloseBrace);
|
||||
// BaseNode testNode = NodeFactory.CreateNumericNode();
|
||||
//}
|
||||
//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 MathEngine.Parser.Parser.Nodes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
using MathEngine.AST.Nodes;
|
||||
|
||||
namespace EngineTests.Parser_Tests.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for testing the TreeNodes
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class NumericNodeTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test two NumericNodes with same generic type can be added
|
||||
/// /// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNumericNodeAdd()
|
||||
{
|
||||
NumericNode<decimal> testNode1 = new(100);
|
||||
NumericNode<decimal> testNode2 = new(100);
|
||||
BaseNode result = testNode1 + testNode2;
|
||||
Assert.AreEqual(result.ToString(), "200");
|
||||
Assert.Equal("200", result.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test two NumericNodes being added of different generic types raises exception
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNumericNodeAddDifferentTypesRaisesException()
|
||||
{
|
||||
NumericNode<int> testNode1 = new(100);
|
||||
@@ -40,7 +35,7 @@ namespace EngineTests.Parser_Tests.Nodes
|
||||
}
|
||||
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>
|
||||
/// Test two NumericNodes with same generic type can be subtracted
|
||||
/// /// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNumericNodeSubtract()
|
||||
{
|
||||
NumericNode<decimal> testNode1 = new(100);
|
||||
NumericNode<decimal> testNode2 = new(100);
|
||||
BaseNode result = testNode1 - testNode2;
|
||||
Assert.AreEqual(result.ToString(), "0");
|
||||
Assert.Equal("0", result.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test two NumericNodes being subtracted of different generic types raises exception
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNumericNodeSubtractDifferentTypesRaisesException()
|
||||
{
|
||||
NumericNode<int> testNode1 = new(100);
|
||||
@@ -71,7 +66,7 @@ namespace EngineTests.Parser_Tests.Nodes
|
||||
}
|
||||
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>
|
||||
/// Test two NumericNodes with same generic type can be multiplied
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNumericNodeMultiply()
|
||||
{
|
||||
NumericNode<decimal> testNode1 = new(100);
|
||||
NumericNode<decimal> testNode2 = new(100);
|
||||
BaseNode result = testNode1 * testNode2;
|
||||
Assert.AreEqual(result.ToString(), "10000");
|
||||
Assert.Equal("10000", result.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test two NumericNodes being multiplied of different generic types raises exception
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNumericNodeMultiplyDifferentTypesRaisesException()
|
||||
{
|
||||
NumericNode<int> testNode1 = new(100);
|
||||
@@ -102,7 +97,7 @@ namespace EngineTests.Parser_Tests.Nodes
|
||||
}
|
||||
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>
|
||||
/// Test two NumericNodes with same generic type can be divided
|
||||
/// /// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNumericNodeDivide()
|
||||
{
|
||||
NumericNode<decimal> testNode1 = new(100);
|
||||
NumericNode<decimal> testNode2 = new(100);
|
||||
BaseNode result = testNode1 / testNode2;
|
||||
Assert.AreEqual(result.ToString(), "1");
|
||||
Assert.Equal("1", result.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test two NumericNodes being divided of different generic types raises exception
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
[Fact]
|
||||
public void TestNumericNodeDividedDifferentTypesRaisesException()
|
||||
{
|
||||
NumericNode<int> testNode1 = new(100);
|
||||
@@ -133,7 +128,7 @@ namespace EngineTests.Parser_Tests.Nodes
|
||||
}
|
||||
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>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
@@ -11,14 +11,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Parser Tests\TreeNodeTests.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
|
||||
<PackageReference Include="coverlet.collector" Version="3.1.2" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="Microsoft.TestPlatform.TestHost" Version="17.14.1" />
|
||||
<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>
|
||||
|
||||
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 MathEngine.Parser.Parser;
|
||||
using Xunit;
|
||||
|
||||
using MathEngine.Parser;
|
||||
using MathEngine.Tokenizer;
|
||||
|
||||
using static MathEngine.Tokenizer.Token;
|
||||
|
||||
namespace EngineTests
|
||||
{
|
||||
/// <summary>
|
||||
/// Class for testing the Parser
|
||||
/// </summary>
|
||||
[TestClass]
|
||||
public class ParserTests
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Test the Parser on a basic List of tokens
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestParserBasicExpression()
|
||||
public static IEnumerable<object[]> TestParserValidExpressionsCases
|
||||
{
|
||||
//Arrange
|
||||
string testString = "3+4";
|
||||
List<Token> testList = Tokeniser.Tokenise(testString);
|
||||
Token three = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||
Token four = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||
Assert.IsNotNull(testList);
|
||||
Stack<Token> expectedStack = new();
|
||||
expectedStack.Push(Token.Plus);
|
||||
expectedStack.Push(four);
|
||||
expectedStack.Push(three);
|
||||
//Act
|
||||
Stack<Token> returnedStack = Parser.Parse(testList);
|
||||
//Assert
|
||||
if (returnedStack.Count != expectedStack.Count)
|
||||
get
|
||||
{
|
||||
Assert.Fail();
|
||||
}
|
||||
else
|
||||
{
|
||||
while (returnedStack.Count > 0)
|
||||
{
|
||||
if (!returnedStack.Pop().Equals(expectedStack.Pop()))
|
||||
{
|
||||
Assert.Fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
/// <summary>
|
||||
/// Test the Parser on a more compilicated basic expression to see if operator precedence is respected
|
||||
/// </summary>
|
||||
[TestMethod]
|
||||
public void TestParserBasicExpressionAllOperators()
|
||||
{
|
||||
//Arrange
|
||||
string testString = "3+4*8-47.2/9";
|
||||
List<Token> testList = Tokeniser.Tokenise(testString);
|
||||
Token three = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||
Token four = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||
Token eight = new("8", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||
Token nine = new("9", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||
Token fourSevenPoint2 = new("47.2", Token.Type.Numeric, Token.NumericType.Decimal, 0);
|
||||
Assert.IsNotNull(testList);
|
||||
Stack<Token> expectedStack = new();
|
||||
expectedStack.Push(Token.Minus);
|
||||
expectedStack.Push(Token.Divide);
|
||||
expectedStack.Push(nine);
|
||||
expectedStack.Push(fourSevenPoint2);
|
||||
expectedStack.Push(Token.Plus);
|
||||
expectedStack.Push(Token.Multiply);
|
||||
expectedStack.Push(eight);
|
||||
expectedStack.Push(four);
|
||||
expectedStack.Push(three);
|
||||
//Act
|
||||
Stack<Token> returnedStack = Parser.Parse(testList);
|
||||
//Assert
|
||||
if (returnedStack.Count != expectedStack.Count)
|
||||
{
|
||||
Assert.Fail();
|
||||
}
|
||||
else
|
||||
{
|
||||
while (returnedStack.Count > 0)
|
||||
{
|
||||
if (!returnedStack.Pop().Equals(expectedStack.Pop()))
|
||||
{
|
||||
Assert.Fail();
|
||||
}
|
||||
}
|
||||
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>
|
||||
/// Test the Parser on valid expressions
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[MemberData(nameof(TestParserValidExpressionsCases))]
|
||||
public void TestParserValidExpressions(string expression, object expected_result)
|
||||
{
|
||||
List<Token> testList = ExpressionTokenizer.Tokenize(expression);
|
||||
|
||||
Queue<Token> returned_rpn_qeue = Parser.Parse(testList);
|
||||
|
||||
Assert.Equal(expected_result, returned_rpn_qeue);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static IEnumerable<object[]> TestParserInValidExpressionsCases
|
||||
{
|
||||
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 }) },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Test the Parser on invalid expressions
|
||||
/// These are expressions that are considered Parsing errors, such
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[MemberData(nameof(TestParserInValidExpressionsCases))]
|
||||
public void TestParserInValidExpressions(string expression, object expected_result)
|
||||
{
|
||||
Assert.True(true);
|
||||
return;
|
||||
//List<Token> testList = ExpressionTokenizer.Tokenize(expression);
|
||||
|
||||
//Queue<Token> returned_rpn_qeue = Parser.Parse(testList);
|
||||
|
||||
//Assert.Equal(expected_result, returned_rpn_qeue);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EngineTests", "EngineTests\EngineTests.csproj", "{096BD3DE-E398-42AD-875F-6BEA469ED78F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MathRunner", "MathRunner\MathRunner.csproj", "{1CFE95A4-74B4-4088-A3DE-9AF3F5FEFF7E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
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}.Release|Any CPU.ActiveCfg = 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
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
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,13 +5,13 @@ using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MathEngine.Parser.Parser
|
||||
namespace MathEngine.AST.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract class representing a Node in a Tree structure
|
||||
/// </summary>
|
||||
internal abstract class BaseNode
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Reference to any child Nodes
|
||||
/// </summary>
|
||||
@@ -1,9 +1,8 @@
|
||||
|
||||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace MathEngine.Parser.Parser.Nodes
|
||||
namespace MathEngine.AST.Nodes
|
||||
{
|
||||
internal class BinaryNode:BaseNode
|
||||
internal class BinaryNode : BaseNode
|
||||
{
|
||||
private BaseNode left;
|
||||
private BaseNode right;
|
||||
@@ -28,8 +27,8 @@ namespace MathEngine.Parser.Parser.Nodes
|
||||
/// <returns></returns>
|
||||
public override BaseNode Evaluate()
|
||||
{
|
||||
BaseNode lhs_val = this.left.Evaluate();
|
||||
BaseNode rhs_val = this.right.Evaluate();
|
||||
BaseNode lhs_val = left.Evaluate();
|
||||
BaseNode rhs_val = right.Evaluate();
|
||||
return op(lhs_val, rhs_val);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,23 @@
|
||||
using MathEngine.Parser.Parser.Nodes;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MathEngine.Parser.Parser
|
||||
namespace MathEngine.AST.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Tree node that stores a function
|
||||
/// </summary>
|
||||
internal class FunctionNode<T>: BaseNode where T : INumber<T>
|
||||
internal class FunctionNode<T> : BaseNode where T : INumber<T>
|
||||
{
|
||||
Action func;
|
||||
List<NumericNode<T>> args;
|
||||
|
||||
public FunctionNode(Action Function, List<NumericNode<T>> arguments)
|
||||
{
|
||||
this.func = Function;
|
||||
func = Function;
|
||||
args = arguments;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using MathEngine.Parser.Parser.Nodes;
|
||||
using MathEngine.Parser.Tokeniser;
|
||||
namespace MathEngine.Parser.Parser
|
||||
using MathEngine.Tokenizer;
|
||||
|
||||
using static MathEngine.Tokenizer.Token;
|
||||
|
||||
namespace MathEngine.AST.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// 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>
|
||||
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);
|
||||
case Token.Type.Subtraction:
|
||||
case TokenType.Subtraction:
|
||||
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);
|
||||
case Token.Type.Division:
|
||||
case TokenType.Division:
|
||||
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);
|
||||
default:
|
||||
throw new NotImplementedException("Attempted to create a BinaryNode with an invalid operation!");
|
||||
@@ -63,23 +66,23 @@ namespace MathEngine.Parser.Parser
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a Node that holds a numerical value
|
||||
/// Creates a Function Node, that is a node that operates a number of arguments
|
||||
/// </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>
|
||||
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 NotImplementedException("Integer Numbers are not implemented at this time");
|
||||
case Token.NumericType.Decimal:
|
||||
return new NumericNode<decimal>(Decimal.Parse(CurrentToken.TokenValue));
|
||||
case Token.NumericType.Complex:
|
||||
throw new NotImplementedException("Complex Numbers are not implemented at this time");
|
||||
default:
|
||||
throw new InvalidDataException("Attempted to create a NumericNode with non numeric data!");
|
||||
}
|
||||
throw new ArgumentException("Token is not of a numeric type");
|
||||
}
|
||||
if (!(decimal.TryParse(NumericToken.Value, out decimal res)))
|
||||
{
|
||||
throw new ArgumentException("Failure to parse number");
|
||||
}
|
||||
return new NumericNode<decimal>(res);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace MathEngine.Parser.Parser.Nodes
|
||||
namespace MathEngine.AST.Nodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a Tree node that can store a numeric value
|
||||
/// </summary>
|
||||
internal class NumericNode<T> : BaseNode where T: INumber<T>
|
||||
internal class NumericNode<T> : BaseNode where T : INumber<T>
|
||||
{
|
||||
private readonly decimal Value;
|
||||
|
||||
@@ -15,8 +15,8 @@ namespace MathEngine.Parser.Parser.Nodes
|
||||
/// <param name="value">The token for the nodes value</param>
|
||||
public NumericNode(decimal value)
|
||||
{
|
||||
this.Children = null;
|
||||
this.Value = value;
|
||||
Children = null;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
protected override BaseNode Add(BaseNode otherNode)
|
||||
@@ -64,7 +64,7 @@ namespace MathEngine.Parser.Parser.Nodes
|
||||
if (otherNode is NumericNode<decimal>)
|
||||
{
|
||||
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");
|
||||
}
|
||||
@@ -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>
|
||||
/// Class for converting from RPN form to an expression tree
|
||||
@@ -12,39 +14,39 @@ namespace MathEngine.Parser.Parser
|
||||
/// </summary>
|
||||
/// <param name="rpnExpression">RPN expression stack to generate an expression tree from</param>
|
||||
/// <returns>An expression Tree that represents the Mathematical expression given by rpnExpression</returns>
|
||||
public static BaseNode TreeFromRPN(Stack<Token> rpnExpression)
|
||||
public static BaseNode TreeFromRPN(Queue<Token> rpnExpression)
|
||||
{
|
||||
Stack<BaseNode> OutputStack = new(rpnExpression.Count);
|
||||
BaseNode Node;
|
||||
Token CurrentToken;
|
||||
while (rpnExpression.Count != 0)
|
||||
{
|
||||
CurrentToken = rpnExpression.Pop();
|
||||
switch (CurrentToken.Token_Type)
|
||||
CurrentToken = rpnExpression.Dequeue();
|
||||
switch (CurrentToken.Type)
|
||||
{
|
||||
case Token.Type.Numeric:
|
||||
case TokenType.Numeric:
|
||||
Node = NodeFactory.CreateNumericNode(CurrentToken);
|
||||
OutputStack.Push(Node);
|
||||
break;
|
||||
// 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.
|
||||
// This should preserve non commutativity
|
||||
case Token.Type.Addition:
|
||||
case Token.Type.Subtraction:
|
||||
case Token.Type.Multiplication:
|
||||
case Token.Type.Division:
|
||||
case Token.Type.Exponentiation:
|
||||
case TokenType.Addition:
|
||||
case TokenType.Subtraction:
|
||||
case TokenType.Multiplication:
|
||||
case TokenType.Division:
|
||||
case TokenType.Exponentiation:
|
||||
BaseNode Right = OutputStack.Pop();
|
||||
BaseNode Left = OutputStack.Pop();
|
||||
Node = NodeFactory.CreateBinaryNode(CurrentToken, Left, Right);
|
||||
OutputStack.Push(Node);
|
||||
break;
|
||||
case Token.Type.UnaryPlus:
|
||||
case Token.Type.UnaryMinus:
|
||||
case TokenType.UnaryPlus:
|
||||
case TokenType.UnaryMinus:
|
||||
//Node = NodeFactory.CreateUnaryNode(CurrentToken, OutputStack.Pop());
|
||||
//OutputStack.Push(Node);
|
||||
break;
|
||||
case Token.Type.Function:
|
||||
case TokenType.Function:
|
||||
Node = NodeFactory.CreateFunctionNode(CurrentToken, OutputStack);
|
||||
OutputStack.Push(Node);
|
||||
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">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Parser\Nodes\NumericIntegerNode.cs" />
|
||||
<Compile Remove="Parser\Nodes\TreeNode.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>EngineTests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||
<PackageReference Include="Microsoft.TestPlatform.TestHost" Version="17.14.1" />
|
||||
<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>
|
||||
|
||||
</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