Skip to content

Instantly share code, notes, and snippets.

@dialupnoises
Created October 31, 2013 05:29
Show Gist options
  • Select an option

  • Save dialupnoises/7244717 to your computer and use it in GitHub Desktop.

Select an option

Save dialupnoises/7244717 to your computer and use it in GitHub Desktop.
Simple token-based calculator in C# that I wrote a few months ago.
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BranBot
{
//i can't believe this works
public class MathCommand
{
public delegate double OperatorDelegate(double num1, double num2);
public static Dictionary<char, OperatorDelegate> Operators;
public static char[] OperatorOrder;
public delegate double FunctionDelegate(double[] arguments);
public static Dictionary<string, FunctionDelegate> Functions;
public static void Initialize()
{
MathCommand.Operators = new Dictionary<char, OperatorDelegate>();
// Please Email My Dad A Shark
MathCommand.OperatorOrder = new char[] { '^', '%', '*', '/', '+', '-' };
MathCommand.Operators['^'] = Operator.PowerOperator; //is this over-engineering? probably
MathCommand.Operators['%'] = Operator.ModulusOperator;
MathCommand.Operators['*'] = Operator.TimesOperator;
MathCommand.Operators['/'] = Operator.DivideOperator;
MathCommand.Operators['+'] = Operator.PlusOperator;
MathCommand.Operators['-'] = Operator.MinusOperator;
MathCommand.Functions = new Dictionary<string, FunctionDelegate>();
MathCommand.Functions["sin"] = Function.Sin;
MathCommand.Functions["cos"] = Function.Cos;
MathCommand.Functions["tan"] = Function.Tan;
MathCommand.Functions["log"] = Function.Log;
MathCommand.Functions["sqrt"] = Function.Sqrt;
}
public static string Parse(string expression)
{
try
{
Tokenizer tokenizer = new Tokenizer(expression);
return tokenizer.RootToken.Parse().ToString();
}
catch (ExpressionParseException e)
{
return "Error: " + e.Message + "!";
}
}
}
public class Operator
{
public static double PlusOperator(double num1, double num2)
{
return num1 + num2;
}
public static double MinusOperator(double num1, double num2)
{
return num1 - num2;
}
public static double TimesOperator(double num1, double num2)
{
return num1 * num2;
}
public static double DivideOperator(double num1, double num2)
{
return num1 / num2;
}
public static double ModulusOperator(double num1, double num2)
{
return num1 % num2;
}
public static double PowerOperator(double num1, double num2)
{
return Math.Pow(num1, num2);
}
}
public class Function
{
public static double Sin(double[] arguments)
{
if (arguments.Length != 1)
throw new ExpressionParseException("Sin takes one argument, " + arguments.Length + " given.");
return Math.Sin(arguments[0]);
}
public static double Cos(double[] arguments)
{
if (arguments.Length != 1)
throw new ExpressionParseException("Cos takes one argument, " + arguments.Length + " given.");
return Math.Cos(arguments[0]);
}
public static double Tan(double[] arguments)
{
if (arguments.Length != 1)
throw new ExpressionParseException("Tan takes one argument, " + arguments.Length + " given.");
return Math.Tan(arguments[0]);
}
public static double Log(double[] arguments)
{
if (arguments.Length != 2)
throw new ExpressionParseException("Log takes two arguments, " + arguments.Length + " given.");
return Math.Log(arguments[0], arguments[1]);
}
public static double Sqrt(double[] arguments)
{
if (arguments.Length != 1)
throw new ExpressionParseException("Sqrt takes one argument, " + arguments.Length + " given.");
return Math.Sqrt(arguments[0]);
}
public static double Abs(double[] arguments)
{
if (arguments.Length != 1)
throw new ExpressionParseException("Abs takes one argument, " + arguments.Length + " given.");
return Math.Abs(arguments[0]);
}
}
public class Tokenizer
{
public Token RootToken;
private Token currentToken;
public Tokenizer(string input)
{
RootToken = currentToken = new Token(TokenType.Root, "", null);
StringReader reader = new StringReader(input);
while (true)
{
int nextCharNum = reader.Read();
if (nextCharNum == -1) //end of expression
{
RootToken.AddChild(new Token(TokenType.EOF, "", null));
break;
}
char nextChar = (char)nextCharNum;
if (Char.IsWhiteSpace(nextChar)) continue;
if (Char.IsNumber(nextChar) || (nextChar == '-' && Char.IsNumber((char)reader.Peek()))) //number, or minus sign before number
{
string fullNum = nextChar.ToString();
while (Char.IsNumber((char)reader.Peek()) || (char)reader.Peek() == '.')
fullNum += (char)reader.Read(); //get the full number
double temp;
if (!double.TryParse(fullNum, out temp))
throw new ExpressionParseException(fullNum + " is not a valid number");
Token t = new Token(TokenType.Number, fullNum, currentToken);
currentToken.AddChild(t);
}
else if (MathCommand.Operators.ContainsKey(nextChar)) //operator
{
Token t = new Token(TokenType.Operator, nextChar.ToString(), currentToken);
currentToken.AddChild(t);
}
else if (nextChar == ')') //end of function or group
{
currentToken = currentToken.Parent;
}
else if (nextChar == '(') //open of group
{
Token t = new Token(TokenType.Group, "", currentToken);
currentToken.AddChild(t);
currentToken = t;
}
else if (nextChar == ',')
{
if (currentToken.Type == TokenType.Root)
throw new ExpressionParseException("Argument seperator while not in function");
Token t = new Token(TokenType.ArgSep, "", currentToken);
currentToken.AddChild(t);
}
else
{
string functionName = nextChar.ToString();
while (true)
{
int c = reader.Peek();
if (c == -1)
throw new ExpressionParseException("End of string present before function end");
if ((char)c == '(') // function name end, start of args
break;
functionName += (char)reader.Read();
}
if (!MathCommand.Functions.ContainsKey(functionName))
throw new ExpressionParseException("Unknown function '" + functionName + "'");
Token t = new Token(TokenType.Function, functionName, currentToken);
currentToken.AddChild(t);
currentToken = t;
}
}
}
}
public class Token
{
private List<Token> children;
private Token parent;
private TokenType type;
private string value;
public Token(TokenType type, string value, Token parent)
{
this.children = new List<Token>();
this.type = type;
this.parent = parent;
this.value = value;
}
public void AddChild(Token child)
{
this.children.Add(child);
}
public double Parse()
{
if (this.Type == TokenType.Number)
return double.Parse(this.Value);
double value = 0;
double childValue = 0;
List<Token> tree = new List<Token>(); //add everything to a new tree to go through later;
foreach (Token child in children) //first, we compute child functions, groups, and numbers
{
switch (child.Type)
{
case TokenType.Function:
case TokenType.Number:
case TokenType.Group:
tree.Add(new Token(TokenType.Number, child.Parse().ToString(), null));
break;
default:
tree.Add(child);
break;
}
}
//now an expression like cos(sin(5) + (sqrt(43) + 4 * 7) + 4) looks like cos(-0.95892427466 + 34.5574385243 + 4)
int resultLocation = 0;
foreach(char op in MathCommand.OperatorOrder) // order of operations? sure
{
for (int i = 0; i < tree.Count; i++)
{
Token child = tree[i];
if (child.Value == op.ToString()) //one operator at a time
{
int prevLocation = i - 1;
int nextLocation = i + 1;
if (tree[i + 1].Type == TokenType.Pointer)
nextLocation = int.Parse(tree[i + 1].Value); // this is probably the worst way to do it
if (tree[i - 1].Type == TokenType.Pointer)
prevLocation = int.Parse(tree[i - 1].Value);
if (i == 0) //no number before operator
throw new ExpressionParseException("Operator found without a preceding number");
if (i == tree.Count - 1)
throw new ExpressionParseException("Operator found at end of string");
if (tree[prevLocation].Type != TokenType.Number || tree[nextLocation].Type != TokenType.Number)
throw new ExpressionParseException("Invalid operator placement; one or both parameters are not numbers!");
double result = MathCommand.Operators[child.Value[0]](double.Parse(tree[prevLocation].Value), double.Parse(tree[nextLocation].Value));
tree[i + 1].value = result.ToString();
tree[i].type = tree[i - 1].type = TokenType.Pointer; //these tokens now point to the true value, in i + 1
tree[i].value = tree[i - 1].value = (i + 1).ToString();
resultLocation = i + 1;
}
}
}
if (tree.Count > 0)
childValue = double.Parse(tree[resultLocation].Value);
// now we have cos(37.5985142496)
if (type == TokenType.Function)
{
List<Token> arguments = new List<Token>();
for (int i = 0; i < tree.Count; i++)
{
Token child = tree[i];
if (child.Type == TokenType.ArgSep)
continue;
if (tree.Count > 1 && tree[i + 1].Type != TokenType.ArgSep)
throw new ExpressionParseException("Number found, expected argument seperator");
arguments.Add(child);
}
double[] args = new double[arguments.Count];
for (int j = 0; j < args.Length; j++)
args[j] = double.Parse(arguments[j].Value);
value = MathCommand.Functions[this.value](args);
}
else
value = childValue;
return value;
}
public TokenType Type
{
get { return this.type; }
}
public Token Parent
{
get { return parent; }
}
public Token[] Children
{
get { return this.children.ToArray(); }
}
public string Value
{
get { return this.value; }
}
}
public enum TokenType
{
Number,
Operator,
Function,
EOF,
Group,
ArgSep,
Pointer,
Root
}
// this is just here so we can differentiate this from an exception thrown by anything else
public class ExpressionParseException : Exception
{
public ExpressionParseException() : base() { }
public ExpressionParseException(string message) : base(message) { }
}
}
@ammarkhan967
Copy link

can you please send me the whole project of it ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment