From f837311da868b7aa71c6337c2de9d2293eb49f6f Mon Sep 17 00:00:00 2001 From: 0xJ1M <112640460+0xJ1M@users.noreply.github.com> Date: Tue, 19 Sep 2023 20:44:34 +0100 Subject: [PATCH] Feature/implement exponentiation (#6) * Modified System to make each numeric node a non-generic for now. Implemented decimal exponentation. This is a draft implementation and is not intended for final release * Reverted change to NumericNode and fixed tests --- .../Parser Tests/Nodes/NodeFactoryTests.cs | 2 - MathEngine/MathEngine/MathEngine.csproj | 1 + .../MathEngine/Parser/Nodes/BaseNode.cs | 22 +++++ .../MathEngine/Parser/Nodes/NodeFactory.cs | 8 +- .../Parser/Nodes/NumericIntegerNode.cs | 90 +++++++++++++++++++ .../MathEngine/Parser/Nodes/NumericNode.cs | 40 +++++---- MathEngine/MathEngine/Parser/Parser/Parser.cs | 1 + .../MathEngine/Parser/Tokeniser/Token.cs | 5 ++ .../MathEngine/Parser/Tokeniser/Tokeniser.cs | 1 + 9 files changed, 149 insertions(+), 21 deletions(-) create mode 100644 MathEngine/MathEngine/Parser/Nodes/NumericIntegerNode.cs diff --git a/MathEngine/EngineTests/Parser Tests/Nodes/NodeFactoryTests.cs b/MathEngine/EngineTests/Parser Tests/Nodes/NodeFactoryTests.cs index 9fbd311..51e1b2b 100644 --- a/MathEngine/EngineTests/Parser Tests/Nodes/NodeFactoryTests.cs +++ b/MathEngine/EngineTests/Parser Tests/Nodes/NodeFactoryTests.cs @@ -78,10 +78,8 @@ namespace EngineTests.Parser_Tests.Nodes [TestMethod] public void TestNodeFactoryNumericNodesOnDefinedTypes() { - Token test_token1 = new("100", Token.Type.Numeric, Token.NumericType.Integer, 0); Token test_token2 = new("100.5", Token.Type.Numeric, Token.NumericType.Decimal, 0); - BaseNode testNode1 = NodeFactory.CreateNumericNode(test_token1); BaseNode testNode2 = NodeFactory.CreateNumericNode(test_token2); } diff --git a/MathEngine/MathEngine/MathEngine.csproj b/MathEngine/MathEngine/MathEngine.csproj index 7824531..ba38089 100644 --- a/MathEngine/MathEngine/MathEngine.csproj +++ b/MathEngine/MathEngine/MathEngine.csproj @@ -8,6 +8,7 @@ + diff --git a/MathEngine/MathEngine/Parser/Nodes/BaseNode.cs b/MathEngine/MathEngine/Parser/Nodes/BaseNode.cs index 2390a8e..af77f73 100644 --- a/MathEngine/MathEngine/Parser/Nodes/BaseNode.cs +++ b/MathEngine/MathEngine/Parser/Nodes/BaseNode.cs @@ -105,6 +105,28 @@ namespace MathEngine.Parser.Parser return lhs.Divide(rhs); } + /// + /// Base method for exponentiating two nodes, returning another BaseNode object + /// + /// The node which will be the power to raise the current instance to + /// + /// + protected virtual BaseNode Exponentiate(BaseNode otherNode) + { + throw new InvalidOperationException("Attempted to call BaseNode _exponentiate, which is invalid!"); + } + + /// + /// Base operator for exponentiating two Base Nodes + /// + /// The left BaseNode + /// The right BaseNode + /// The exponentiating of the two BaseNodes, as defined by the calling type + public static BaseNode operator ^(BaseNode lhs, BaseNode rhs) + { + return lhs.Exponentiate(rhs); + } + /// /// Abstract Base method that evaluates the current Node /// diff --git a/MathEngine/MathEngine/Parser/Nodes/NodeFactory.cs b/MathEngine/MathEngine/Parser/Nodes/NodeFactory.cs index d631bc1..cf3cff6 100644 --- a/MathEngine/MathEngine/Parser/Nodes/NodeFactory.cs +++ b/MathEngine/MathEngine/Parser/Nodes/NodeFactory.cs @@ -27,7 +27,7 @@ namespace MathEngine.Parser.Parser case Token.Type.Division: return new BinaryNode(LeftBranch, RightBranch, (a, b) => a / b); case Token.Type.Exponentiation: - throw new NotImplementedException("Exponentiation is not implemented 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!"); } @@ -43,11 +43,11 @@ namespace MathEngine.Parser.Parser switch (CurrentToken.NumericalType) { case Token.NumericType.Integer: - return new NumericNode(Int64.Parse(CurrentToken.TokenValue)); + throw new NotImplementedException("Integer Numbers are not implemented at this time"); case Token.NumericType.Decimal: - return new NumericNode(Decimal.Parse(CurrentToken.TokenValue)); + return new NumericNode(Decimal.Parse(CurrentToken.TokenValue)); case Token.NumericType.Complex: - throw new NotImplementedException("Complex Numbers are not implemented at this time"); + throw new NotImplementedException("Complex Numbers are not implemented at this time"); default: throw new InvalidDataException("Attempted to create a NumericNode with non numeric data!"); } diff --git a/MathEngine/MathEngine/Parser/Nodes/NumericIntegerNode.cs b/MathEngine/MathEngine/Parser/Nodes/NumericIntegerNode.cs new file mode 100644 index 0000000..ff72f69 --- /dev/null +++ b/MathEngine/MathEngine/Parser/Nodes/NumericIntegerNode.cs @@ -0,0 +1,90 @@ +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; + + /// + /// Initialises a new instance of a NumericNode with a given Token + /// + /// The token for the nodes value + 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"); + } + + /// + /// Evaluates the Numeric Node by simply returning the current instance + /// + /// + public override BaseNode Evaluate() + { + return this; + } + + public override string ToString() + { + return Value.ToString(); + } + + } +} diff --git a/MathEngine/MathEngine/Parser/Nodes/NumericNode.cs b/MathEngine/MathEngine/Parser/Nodes/NumericNode.cs index 0319948..259fac8 100644 --- a/MathEngine/MathEngine/Parser/Nodes/NumericNode.cs +++ b/MathEngine/MathEngine/Parser/Nodes/NumericNode.cs @@ -5,15 +5,15 @@ namespace MathEngine.Parser.Parser.Nodes /// /// Represents a Tree node that can store a numeric value /// - internal class NumericNode : BaseNode where T: INumber + internal class NumericNode : BaseNode where T: INumber { - private readonly T Value; + private readonly decimal Value; /// /// Initialises a new instance of a NumericNode with a given Token /// /// The token for the nodes value - public NumericNode(T value) + public NumericNode(decimal value) { this.Children = null; this.Value = value; @@ -21,40 +21,50 @@ namespace MathEngine.Parser.Parser.Nodes protected override BaseNode Add(BaseNode otherNode) { - if (otherNode is NumericNode) + if (otherNode is NumericNode) { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode(Value + rhs.Value); + NumericNode rhs = (NumericNode)otherNode; + return new NumericNode(Value + rhs.Value); } throw new InvalidOperationException("Attempted Invalid operation"); } protected override BaseNode Subtract(BaseNode otherNode) { - if (otherNode is NumericNode) + if (otherNode is NumericNode) { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode(Value - rhs.Value); + NumericNode rhs = (NumericNode)otherNode; + return new NumericNode(Value - rhs.Value); } throw new InvalidOperationException("Attempted Invalid operation"); } protected override BaseNode Multiply(BaseNode otherNode) { - if (otherNode is NumericNode) + if (otherNode is NumericNode) { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode(Value * rhs.Value); + NumericNode rhs = (NumericNode)otherNode; + return new NumericNode(Value * rhs.Value); } throw new InvalidOperationException("Attempted Invalid operation"); } protected override BaseNode Divide(BaseNode otherNode) { - if (otherNode is NumericNode) + if (otherNode is NumericNode) { - NumericNode rhs = (NumericNode)otherNode; - return new NumericNode(Value / rhs.Value); + NumericNode rhs = (NumericNode)otherNode; + return new NumericNode(Value / rhs.Value); + } + throw new InvalidOperationException("Attempted Invalid operation"); + } + + protected override BaseNode Exponentiate(BaseNode otherNode) + { + if (otherNode is NumericNode) + { + NumericNode rhs = (NumericNode)otherNode; + return new NumericNode((decimal)(Math.Pow((double)Value, (double)(rhs.Value)))); } throw new InvalidOperationException("Attempted Invalid operation"); } diff --git a/MathEngine/MathEngine/Parser/Parser/Parser.cs b/MathEngine/MathEngine/Parser/Parser/Parser.cs index b45ae77..45160d5 100644 --- a/MathEngine/MathEngine/Parser/Parser/Parser.cs +++ b/MathEngine/MathEngine/Parser/Parser/Parser.cs @@ -119,6 +119,7 @@ namespace MathEngine.Parser.Parser 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()); diff --git a/MathEngine/MathEngine/Parser/Tokeniser/Token.cs b/MathEngine/MathEngine/Parser/Tokeniser/Token.cs index 52ae344..8f6ede4 100644 --- a/MathEngine/MathEngine/Parser/Tokeniser/Token.cs +++ b/MathEngine/MathEngine/Parser/Tokeniser/Token.cs @@ -25,6 +25,11 @@ /// public static readonly Token Divide = new("/", Type.Division, NumericType.NaN, 0); + /// + /// Represents the token for ^ + /// + public static readonly Token Exponentiation = new("^", Type.Exponentiation, NumericType.NaN, 0); + /// /// Enum representing the token type /// diff --git a/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs b/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs index 827c30c..0a3b57f 100644 --- a/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs +++ b/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs @@ -31,6 +31,7 @@ '-' => Token.Minus, '*' => Token.Multiply, '/' => Token.Divide, + '^' => Token.Exponentiation, _ => throw new Exception(String.Format("Character {0} is not a defined operator", curChar)), }; }