This parser is made for part of larger trivia game, and only allowed numbers are positive integers. So negative or decimal numbers should return -1 as sign of error.
I'm pretty new to programming and have never encountered something similar. I tried my best. I guess its not an optimal solution, but I would like to get some feedback on what is good, what to change, and overall quality of this code and its performance.
using System.Data;
using System.Text;
namespace Parser
{
class MathParser
{
public const string validSigns = "+-*/()";
public int Parse(string input)
{
var tokens = Tokenize(input);
int result = Eval(tokens, 0);
return result;
}
public int Eval(List<string> tokens, int pos)
{
while (pos < tokens.Count)
{
if (tokens[pos] == "(")
{
int openB = 0;
int closeB = 0;
for (int i = pos; i < tokens.Count; i++)
{
if (tokens[i] == "(")
{
openB++;
}
else if (tokens[i] == ")")
{
closeB++;
if (openB == closeB)
{
tokens.RemoveAt(i);
tokens.RemoveAt(pos);
var expressionTokens = tokens.Skip(pos).Take(i - pos - 1).ToList();
int result = Eval(expressionTokens, 0);
if (result < 0)
{
return -1;
}
tokens.RemoveRange(pos + 1, i - pos - 2);
tokens[pos] = result.ToString();
pos = 0;
break;
}
}
}
}
pos++;
}
for (int i = 0; i < tokens.Count; i++)
{
if (tokens[i] == "*" || tokens[i] == "/")
{
int left = Convert.ToInt32(tokens[i - 1]);
int right = Convert.ToInt32(tokens[i + 1]);
if (tokens[i] == "/" && left % right != 0)
{
return -1;
}
int result = tokens[i] == "*" ? left * right : left / right;
if (result < 0)
{
return -1;
}
tokens[i] = result.ToString();
tokens.RemoveAt(i + 1);
tokens.RemoveAt(i - 1);
i = 0;
}
}
for (int i = 0; i < tokens.Count; i++)
{
if (tokens[i] == "+" || tokens[i] == "-")
{
int left = Convert.ToInt32(tokens[i - 1]);
int right = Convert.ToInt32(tokens[i + 1]);
int result = tokens[i] == "+" ? left + right : left - right;
if (result < 0)
{
return -1;
}
tokens[i] = result.ToString();
tokens.RemoveAt(i + 1);
tokens.RemoveAt(i - 1);
i = 0;
}
}
return Convert.ToInt32(tokens[0]);
}
public List<string> Tokenize(string expression)
{
var tokens = new List<string>();
expression = RemoveWhiteSpace(expression);
var tempN = new StringBuilder();
for (int i = 0; i < expression.Length; i++)
{
if (isNumber(expression[i]))
{
tempN.Append(expression[i]);
}
else
{
if (tempN.Length > 0)
{
tokens.Add(tempN.ToString());
tempN.Clear();
}
if (isValidSign(expression[i]))
{
tokens.Add(expression[i].ToString());
}
}
}
if (tempN.Length > 0)
{
tokens.Add(tempN.ToString());
}
return tokens;
}
public void print(List<string> tokens)
{
foreach (var token in tokens)
Console.Write(" {0} ", token);
System.Console.WriteLine();
}
public string RemoveWhiteSpace(string input)
{
return new string(input
.Where(c => !Char.IsWhiteSpace(c))
.ToArray());
}
public bool isValidSign(char c)
{
for (int i = 0; i < validSigns.Length; i++)
{
if (c == validSigns[i])
{
return true;
}
}
return false;
}
public bool isNumber(char c)
{
return char.IsDigit(c);
}
public bool isNumber(string s)
{
return int.TryParse(s, out _);
}
}
}
At first I tried building it without slicing down list for each bracket (instead I would provide indexes for start and end of bracket, but deleting whole bracket was hell), but it would throw errors at nested brackets, and I just couldn't follow it up, so I deleted it all and tried to built it this way, which seems to have succeeded.
Console.Write
next to aSystem.Console.WriteLine
- no need to duplicate the namespace you've already referenced. In some cases, you surround single statements with braces, others not - programmers are generally appreciative of braces everywhere to make maintenance clear. \$\endgroup\$signs
are actuallyoperators
. No biggie, but better to be clear about their purpose... (And the numbers areoperands
.) \$\endgroup\$