From fb81730adb711d85a33148286055f26ddc9b0ae1 Mon Sep 17 00:00:00 2001 From: 0xJ1M <112640460+0xJ1M@users.noreply.github.com> Date: Wed, 20 Sep 2023 18:00:05 +0100 Subject: [PATCH] Work in progress: To continue on this ticket additional work has to be done enable dynamic collection and organisation of built-in types to be callable (#7) --- MathEngine/MathEngine/Evaluator/Evaluator.cs | 74 ------------------- .../MathEngine/Parser/Nodes/FunctionNode.cs | 30 ++++++++ .../MathEngine/Parser/Nodes/NodeFactory.cs | 29 ++++++++ MathEngine/MathEngine/Parser/Parser/Parser.cs | 18 +++++ .../MathEngine/Parser/Parser/TreeGenerator.cs | 4 + .../MathEngine/Parser/Tokeniser/Token.cs | 27 +++++++ .../MathEngine/Parser/Tokeniser/Tokeniser.cs | 35 ++++++++- 7 files changed, 140 insertions(+), 77 deletions(-) delete mode 100644 MathEngine/MathEngine/Evaluator/Evaluator.cs create mode 100644 MathEngine/MathEngine/Parser/Nodes/FunctionNode.cs diff --git a/MathEngine/MathEngine/Evaluator/Evaluator.cs b/MathEngine/MathEngine/Evaluator/Evaluator.cs deleted file mode 100644 index 7496f40..0000000 --- a/MathEngine/MathEngine/Evaluator/Evaluator.cs +++ /dev/null @@ -1,74 +0,0 @@ -using MathEngine.Parser.Parser; -using MathEngine.Parser.Tokeniser; - -namespace MathEngine.Parser.Evaluator -{ - /// - /// Class that evaluate TreeNodes - /// - internal class Evaluator - { - /*public static TreeNode? Evaluate(TreeNode rootNode) - { - if (rootNode == null) - { - return null; - } - else // To evaluate we go anti-clockwise around the tree - { - TreeNode? Root, LeftBranch, RightBranch; - if (rootNode.NodeValue.Token_Type == Token.Type.Numeric) - return rootNode; - else - { //For now these can't be null, this will need to be updated in the future - LeftBranch = Evaluate_Tree_Branch(rootNode.GetChildNode(0)); - RightBranch = Evaluate_Tree_Branch(rootNode.GetChildNode(1)); - Root = Evaluate_Operator(rootNode.NodeValue, LeftBranch, RightBranch); - return Root; - } - } - } - - - /// - /// Evaluates branches of a given tree - /// - /// The TreeNode branch to evaluate - /// Returns the Evaluation of the Branch - private static TreeNode Evaluate_Tree_Branch(TreeNode Branch) - { - TreeNode Root, LeftBranch, RightBranch; - if (Branch.NodeValue.Token_Type == Token.Type.Numeric) - return Branch; - else - { - LeftBranch = Evaluate_Tree_Branch(Branch.GetChildNode(0)); - RightBranch = Evaluate_Tree_Branch(Branch.GetChildNode(1)); - // We finally combine the computed branches with the operator that links them and return the result - Root = Evaluate_Operator(Branch.NodeValue, LeftBranch, RightBranch); - return Root; - } - } - - /// - /// Evlautes a binary node where the root node is an operator and given two branches the left and the right - /// - /// - /// - /// - /// Returns the evaluated value of the operator as a TreeNode - private static TreeNode Evaluate_Operator(Token Operator_Token, TreeNode Left_Branch, TreeNode Right_Branch) - { - decimal lhs = decimal.Parse(Left_Branch.NodeValue.TokenValue); - decimal rhs = decimal.Parse(Right_Branch.NodeValue.TokenValue); - return Operator_Token.Token_Type switch - { - Token.Type.Addition => new TreeNode(new Token((lhs + rhs).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)), - Token.Type.Subtraction => new TreeNode(new Token((lhs - rhs).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)), - Token.Type.Multiplication => new TreeNode(new Token(((decimal)(lhs * rhs)).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)), - Token.Type.Division => new TreeNode(new Token(((decimal)(lhs / rhs)).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)), - _ => throw new Exception("Potentially invalid token?"), - }; - }*/ - } -} diff --git a/MathEngine/MathEngine/Parser/Nodes/FunctionNode.cs b/MathEngine/MathEngine/Parser/Nodes/FunctionNode.cs new file mode 100644 index 0000000..509a854 --- /dev/null +++ b/MathEngine/MathEngine/Parser/Nodes/FunctionNode.cs @@ -0,0 +1,30 @@ +using MathEngine.Parser.Parser.Nodes; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; + +namespace MathEngine.Parser.Parser +{ + /// + /// Represents a Tree node that stores a function + /// + internal class FunctionNode: BaseNode where T : INumber + { + Action func; + List> args; + + public FunctionNode(Action Function, List> arguments) + { + this.func = Function; + args = arguments; + } + + public override BaseNode Evaluate() + { + throw new NotImplementedException(); + } + } +} diff --git a/MathEngine/MathEngine/Parser/Nodes/NodeFactory.cs b/MathEngine/MathEngine/Parser/Nodes/NodeFactory.cs index cf3cff6..c159d56 100644 --- a/MathEngine/MathEngine/Parser/Nodes/NodeFactory.cs +++ b/MathEngine/MathEngine/Parser/Nodes/NodeFactory.cs @@ -7,6 +7,8 @@ namespace MathEngine.Parser.Parser /// internal class NodeFactory { + + /// /// Creates a binary TreeNode, that is a node with a root value and two children /// @@ -33,6 +35,33 @@ namespace MathEngine.Parser.Parser } } + /// + /// Creates a Function Node, that is a node that operates a number of arguments + /// + /// The function to create the node for + /// Reference to Stack of BaseNode objects to take the arguments from + /// + public static BaseNode CreateFunctionNode(Token FunctionToken, Stack ArgumentStack) + { + throw new NotImplementedException("Not implemented at this time"); + /* Check that the function exists, if it does we then check that the number of arguments + * for the function is are avaliable on the ArgumentStack. If so we can construct the Node. + * Otherwise, either the function does not exist or there is an argument mismatch */ + /*try + { + functionAction = GetFunction(FunctionToken, out int functionArity); + if (functionArity >= ArgumentStack.Count) + { + throw new ArgumentException("Number of required arguments is greater than avaliable arugments"); + } + } + catch (Exception) + { + + throw; + }*/ + } + /// /// Returns a Node that holds a numerical value /// diff --git a/MathEngine/MathEngine/Parser/Parser/Parser.cs b/MathEngine/MathEngine/Parser/Parser/Parser.cs index 45160d5..4d45c9a 100644 --- a/MathEngine/MathEngine/Parser/Parser/Parser.cs +++ b/MathEngine/MathEngine/Parser/Parser/Parser.cs @@ -126,6 +126,24 @@ namespace MathEngine.Parser.Parser } 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; } } diff --git a/MathEngine/MathEngine/Parser/Parser/TreeGenerator.cs b/MathEngine/MathEngine/Parser/Parser/TreeGenerator.cs index 11da6f6..1068af7 100644 --- a/MathEngine/MathEngine/Parser/Parser/TreeGenerator.cs +++ b/MathEngine/MathEngine/Parser/Parser/TreeGenerator.cs @@ -44,6 +44,10 @@ namespace MathEngine.Parser.Parser //Node = NodeFactory.CreateUnaryNode(CurrentToken, OutputStack.Pop()); //OutputStack.Push(Node); break; + case Token.Type.Function: + Node = NodeFactory.CreateFunctionNode(CurrentToken, OutputStack); + OutputStack.Push(Node); + break; } } return OutputStack.Pop(); diff --git a/MathEngine/MathEngine/Parser/Tokeniser/Token.cs b/MathEngine/MathEngine/Parser/Tokeniser/Token.cs index 8f6ede4..c95ea29 100644 --- a/MathEngine/MathEngine/Parser/Tokeniser/Token.cs +++ b/MathEngine/MathEngine/Parser/Tokeniser/Token.cs @@ -5,6 +5,7 @@ /// internal readonly struct Token { + /// /// Represents the token for + /// @@ -26,10 +27,20 @@ public static readonly Token Divide = new("/", Type.Division, NumericType.NaN, 0); /// + /// Represents the token for ( + /// + public static readonly Token LeftParenthesis = new Token("(", Token.Type.LeftParenthesis, Token.NumericType.NaN, 0); + + /// + /// Represents the token for ) + /// + public readonly static Token RightParenthesis = new Token(")", Token.Type.RightParenthesis, Token.NumericType.NaN, 0); + /// Represents the token for ^ /// public static readonly Token Exponentiation = new("^", Type.Exponentiation, NumericType.NaN, 0); + /// /// Enum representing the token type /// @@ -138,6 +149,22 @@ this.Arity = FunctionArity; } + + /// + /// Initializes a new instance of the Tokeniser.Token structure with a given TokenValue, TokenType, TokenNumericType and Arity + /// + /// Char representing the value of the token + /// The type that the token instance represents + /// The numeric type of the token + /// The token Arity + 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 /// /// Debug String; Used to give a string representation of a token diff --git a/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs b/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs index 0a3b57f..c4cc935 100644 --- a/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs +++ b/MathEngine/MathEngine/Parser/Tokeniser/Tokeniser.cs @@ -6,6 +6,7 @@ internal static class Tokeniser { private static readonly List Operators = new(new char[] { '+', '-', '*', '/', '^' }); + private static readonly List Parenthesis = new(new char[] { '(', ')'}); /// /// Gets the next non-whitespace char or returns the null terminator is at EOS (End of stream) @@ -36,6 +37,16 @@ }; } + static private Token GetParenthesisToken(char curChar) + { + return curChar switch + { + '(' => Token.LeftParenthesis, + ')' => Token.RightParenthesis, + _ => throw new Exception(String.Format("Character {0} is not Parenthesis", curChar)), + }; + } + /// /// Tokenises a given Mathematical expression given as a string to a list of tokens /// @@ -65,9 +76,14 @@ 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)) + if (Char.IsDigit(curChar)) { bool hasDecimalPlace = false; Int32 tempIndex = currentIndex; @@ -86,14 +102,27 @@ 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)); + 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 + 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 } }