I have created a console program where users type in a math equation such as 5+4-6
or (5+16(8-4))/16
that follows order of operations. The equation is sent to a char array that is then parsed. If a #
or .
is encountered, the char is placed into a buffer. The buffer is tokenized into a token class, followed by the operator itself being tokenized. The program works beautifully and as expected. Exceptions and errors still need to be handled where right now it is only writing to the console and continuing.
What I am looking to do next is add in higher functions, more in-line with a scientific calculator, starting with the constants such as e and pi, then moving into trig functions. It is in handling the functions that I am having an issue with.
Would you recommend storing the chars in a buffer similar to the #'s and treat them as a function if a )
is found immediately following, and a constant otherwise? What would you suggest? Also, what could I improve upon?
PROGRAM.CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CalcTest.Core;
namespace CalcTest
{
class Program
{
static Calculator calculator;
static void Main(string[] args)
{
calculator = new Calculator();
calculator.Entry();
}
}
}
ENUMS.CS
using CalcTest;
using CalcTest.Core;
namespace CalcTest.Core
{
public enum TokenType
{
Value,
Operand,
Function,
Paramater,
Variable
}
}
TOKEN.CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CalcTest.Core
{
public class Token
{
private TokenType type;
private string val;
public TokenType TYPE
{
get { return type; }
set { type = value; }
}
public string VALUE
{
get { return val; }
set { val = value; }
}
public Token(TokenType t, string s)
{
this.TYPE = t;
this.VALUE = s;
//Write();
}
public override string ToString()
{
return String.Format("Token:= Type: {0}; Value: {1}", TYPE.ToString(), VALUE);
}
private void Write()
{
Console.WriteLine(this.ToString());
}
}
}
CALCULATOR.CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CalcTest.Core
{
public class Calculator
{
private Stack<Token> tokenStack;
private Queue<Token> tokenQue;
private char[] buffer;
private int bufferLoc;
private TokenType? priorType;
public Calculator()
{
tokenQue = new Queue<Token>();
tokenStack = new Stack<Token>();
buffer = new char[100];
bufferLoc = 0;
priorType = null;
}
#region BasicFunctions
private Token Addition(Token t1, Token t2)
{
//Console.WriteLine("Addition:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
double arg1 = double.Parse(t1.VALUE);
double arg2 = double.Parse(t2.VALUE);
double sum = arg1 + arg2;
return CreateToken(TokenType.Value, sum.ToString());
}//end Addition
private Token Subtract(Token t1, Token t2)
{
//Console.WriteLine("Subtraction:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
double arg1 = double.Parse(t1.VALUE);
double arg2 = double.Parse(t2.VALUE);
double sub = arg1 - arg2;
return CreateToken(TokenType.Value, sub.ToString());
}//end Subtract
private Token Multiplication(Token t1, Token t2)
{
//Console.WriteLine("Multiplication:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
double arg1 = double.Parse(t1.VALUE);
double arg2 = double.Parse(t2.VALUE);
double multi = arg1 * arg2;
return CreateToken(TokenType.Value, multi.ToString());
}//end Multiplication
private Token Division(Token t1, Token t2)
{
//Console.WriteLine("Division:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
double arg1 = double.Parse(t1.VALUE);
double arg2 = double.Parse(t2.VALUE);
double div = arg1/arg2;
return CreateToken(TokenType.Value, div.ToString());
}//end Division
private Token Power(Token t1, Token t2)
{
//Console.WriteLine("Power:= Arg1: {0}; Arg2: {1}", t1.VALUE, t2.VALUE);
double arg1 = double.Parse(t1.VALUE);
double arg2 = double.Parse(t2.VALUE);
double power = Math.Pow(arg1,arg2);
return CreateToken(TokenType.Value, power.ToString());
}//end Power
#endregion
public void Entry()
{
char[] equation = new char[1];
while (true)
{
Array.Clear(equation, 0, equation.Length);
equation = Console.ReadLine().ToCharArray();
ParseEquation(equation);
foreach (var t in tokenQue)
{
Console.Write(t.VALUE + ", ");
}
Console.WriteLine();
EvalEquation();
if (tokenStack.Count > 0)
Console.WriteLine(tokenStack.Pop().VALUE);
else
Console.WriteLine("Nothing returned");
tokenStack.Clear();
tokenQue.Clear();
}
}//end Entry
#region EvalFunctions
private void EvalEquation()
{
Token temp = null;
while (tokenQue.Count != 0)
{
temp = tokenQue.Dequeue();
if (temp.TYPE == TokenType.Value)
tokenStack.Push(temp);
else
OpperationSwitch(temp);
}
}//end EvalEquation
private void OpperationSwitch(Token t)
{
Token ret = null;
if (tokenStack.Count >= 2)
{
Token t2 = tokenStack.Pop();
Token t1 = tokenStack.Pop();
switch (t.VALUE)
{
case ("*"):
ret = Multiplication(t1, t2);
break;
case ("/"):
ret = Division(t1, t2);
break;
case ("+"):
ret = Addition(t1, t2);
break;
case ("-"):
ret = Subtract(t1, t2);
break;
case ("^"):
ret = Power(t1, t2);
break;
}
if (ret != null)
tokenStack.Push(ret);
}
else
{
Console.WriteLine("Error in OpperationSwitch");
//error code
}
}//end OpperationSwitch
#endregion
#region ParserFunctions
private void ParseEquation(char[] equation)
{
for (int i = 0; i < equation.Length; i++)
{
priorType = null;
//Console.WriteLine("ParseEquation:= i: {0} ; Char: {1}", i, equation[i]);
if (char.IsDigit(equation[i]) || equation[i] == '.')
{
//executes if # or .
AddToBuffer(equation[i]);
}
else
{
//if buffer has data, create token, place in queue, clear buffer, and set prior type.
if (bufferLoc != 0)
{
tokenQue.Enqueue(CreateToken(TokenType.Value, new string(buffer, 0, bufferLoc)));
Array.Clear(buffer, 0, bufferLoc);
bufferLoc = 0;
priorType = TokenType.Value;
}
//handles operands
OperationHandler(equation[i]);
}//end else-if
}//end while
//creates a token of anything remaining in the buffer, and queues it
if (bufferLoc != 0)
{
tokenQue.Enqueue(CreateToken(TokenType.Value, new string(buffer, 0, bufferLoc)));
Array.Clear(buffer, 0, bufferLoc);
bufferLoc = 0;
}
//moves anything remaining on the tokenStack to the que
while (tokenStack.Count > 0)
{
Token temp = tokenStack.Pop();
if (temp.VALUE == "(")
{
//will only run in case of unpaired ()
Console.WriteLine("Error with '(' in ParseEquation");
//error code
}
tokenQue.Enqueue(temp);
}
}//end ParseEquation
private void OperationHandler(char opp)
{
if (opp == ')')
{
//runs if the opp is )
//Console.WriteLine("Entering ')' section of OperationHandler");
Token temp = null;
bool found = false;
//cycles until the most recent ( is found. If not found, error.
while (tokenStack.Count != 0)
{
temp = tokenStack.Pop();
if (temp.VALUE == "(")
{
found = true;
return;
}
else
{
tokenQue.Enqueue(temp);
}
}
if (!found)
{
Console.WriteLine("Error with '(' section of OperationHandler");
//error code
}
}
else
{
Token temp = CreateToken(TokenType.Operand, opp.ToString());
//creates a multiplication incase of implicite multiplication. Ex: 5(4) => 5*4
if (opp == '(' && priorType == TokenType.Value)
{
//Console.WriteLine("Creating a multiplication");
tokenStack.Push(CreateToken(TokenType.Operand, "*"));
}
if (tokenStack.Count == 0)
{
//runs if stack is empty
tokenStack.Push(temp);
}
else
{
OrderOfOpp(temp);
}
}
}//end OperationHandler
private void OrderOfOpp(Token t)
{
string tempVal = null;
Dictionary<string, int> oppDic = new Dictionary<string, int>() {{"(",4 }, {"^",3},{"*",2},{"/",2},{"+",1},{"-",1}};
bool done = false;
while (!done || tokenStack.Count == 0)
{
tempVal = tokenStack.Peek().VALUE;
if (tempVal == "(")
{
//if most recent push to stack was (, auto push t to stack
tokenStack.Push(t);
done = true;
}
else
{
if (oppDic[t.VALUE] >= oppDic[tempVal])
{
//if the t token has a higher precident then the tempVal (Obtained from peek), pushes t to que
tokenStack.Push(t);
done = true;
}
else
{
//if the tempVal has a higher precident then pop and enqueue
tokenQue.Enqueue(tokenStack.Pop());
}
}
if (tokenStack.Count == 0)
{
//if stack is empty, push to stack
tokenStack.Push(t);
done = true;
}
}//end while
if (!done)
{
Console.WriteLine("Error in OrderOfOpp");
//error code
}
}//end OrderOfOpp
#endregion
#region HelperFunctions
private void AddToBuffer(char add)
{
buffer[bufferLoc] = add;
bufferLoc++;
}//end AddToBuffer
private Token CreateToken(TokenType type, string value)
{
return new Token(type, value);
}//end CreateToken
#endregion
}
}