Created
October 31, 2013 05:29
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) { } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
can you please send me the whole project of it ?