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 (#5)

This commit is contained in:
0xJ1M
2023-09-12 22:02:25 +01:00
committed by GitHub
parent 461a60ff77
commit 9c6f5cb483
7 changed files with 140 additions and 77 deletions

View File

@@ -1,74 +0,0 @@
using MathEngine.Parser.Parser;
using MathEngine.Parser.Tokeniser;
namespace MathEngine.Parser.Evaluator
{
/// <summary>
/// Class that evaluate TreeNodes
/// </summary>
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;
}
}
}
/// <summary>
/// Evaluates branches of a given tree
/// </summary>
/// <param name="Branch">The TreeNode branch to evaluate</param>
/// <returns>Returns the Evaluation of the Branch</returns>
private static TreeNode Evaluate_Tree_Branch(TreeNode Branch)
{
TreeNode Root, LeftBranch, RightBranch;
if (Branch.NodeValue.Token_Type == Token.Type.Numeric)
return Branch;
else
{
LeftBranch = Evaluate_Tree_Branch(Branch.GetChildNode(0));
RightBranch = Evaluate_Tree_Branch(Branch.GetChildNode(1));
// We finally combine the computed branches with the operator that links them and return the result
Root = Evaluate_Operator(Branch.NodeValue, LeftBranch, RightBranch);
return Root;
}
}
/// <summary>
/// Evlautes a binary node where the root node is an operator and given two branches the left and the right
/// </summary>
/// <param name="Operator_Token"></param>
/// <param name="Left_Branch"></param>
/// <param name="Right_Branch"></param>
/// <returns>Returns the evaluated value of the operator as a TreeNode</returns>
private static TreeNode Evaluate_Operator(Token Operator_Token, TreeNode Left_Branch, TreeNode Right_Branch)
{
decimal lhs = decimal.Parse(Left_Branch.NodeValue.TokenValue);
decimal rhs = decimal.Parse(Right_Branch.NodeValue.TokenValue);
return Operator_Token.Token_Type switch
{
Token.Type.Addition => new TreeNode(new Token((lhs + rhs).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
Token.Type.Subtraction => new TreeNode(new Token((lhs - rhs).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
Token.Type.Multiplication => new TreeNode(new Token(((decimal)(lhs * rhs)).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
Token.Type.Division => new TreeNode(new Token(((decimal)(lhs / rhs)).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
_ => throw new Exception("Potentially invalid token?"),
};
}*/
}
}

View File

@@ -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
{
/// <summary>
/// Represents a Tree node that stores a function
/// </summary>
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;
args = arguments;
}
public override BaseNode Evaluate()
{
throw new NotImplementedException();
}
}
}

View File

@@ -7,6 +7,8 @@ namespace MathEngine.Parser.Parser
/// </summary>
internal class NodeFactory
{
/// <summary>
/// Creates a binary TreeNode, that is a node with a root value and two children
/// </summary>
@@ -33,6 +35,33 @@ namespace MathEngine.Parser.Parser
}
}
/// <summary>
/// Creates a Function Node, that is a node that operates a number of arguments
/// </summary>
/// <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 CreateFunctionNode(Token FunctionToken, Stack<BaseNode> 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;
}*/
}
/// <summary>
/// Returns a Node that holds a numerical value
/// </summary>

View File

@@ -125,6 +125,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;
}
}

View File

@@ -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();

View File

@@ -5,6 +5,7 @@
/// </summary>
internal readonly struct Token
{
/// <summary>
/// Represents the token for +
/// </summary>
@@ -25,6 +26,16 @@
/// </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);
/// <summary>
/// Enum representing the token type
/// </summary>
@@ -133,6 +144,22 @@
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

View File

@@ -6,6 +6,7 @@
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)
@@ -35,6 +36,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)),
};
}
/// <summary>
/// Tokenises a given Mathematical expression given as a string to a list of tokens
/// </summary>
@@ -64,6 +75,11 @@
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))
@@ -85,14 +101,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
}
}