Created
July 14, 2014 20:34
-
-
Save apetrovic/aa0f411b5dcb1451c9db to your computer and use it in GitHub Desktop.
Simple calculator in C#
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.Collections; | |
| using System.Collections.Generic; | |
| using System.Linq; | |
| using System.Text; | |
| using System.Threading.Tasks; | |
| namespace Calc { | |
| // -- tokenizer | |
| public interface IToken { } | |
| public interface IOperatorToken : IToken { | |
| int Priority { get; } | |
| } | |
| public class OperatorToken : IOperatorToken { | |
| public char Operator { get; private set; } | |
| public int Priority { get; private set; } | |
| public Func<double, double, double> Calc { get; private set; } | |
| public OperatorToken(char oper, int priority, Func<double, double, double> calc) { | |
| Operator = oper; | |
| Priority = priority; | |
| Calc = calc; | |
| } | |
| private static readonly Dictionary<char, OperatorToken> _operators = new Dictionary<char, OperatorToken> { | |
| {'+', new OperatorToken( '+', 0, (a, b) => a + b )}, | |
| {'-', new OperatorToken( '-', 0, (a, b) => a - b )}, | |
| {'*', new OperatorToken( '*', 1, (a, b) => a * b )}, | |
| {'/', new OperatorToken( '/', 1, (a, b) => a / b )}, | |
| }; | |
| public static IToken Make(string input, ref int position) { | |
| OperatorToken ret; | |
| if(_operators.TryGetValue( input[position], out ret )) { | |
| ++position; | |
| return ret; | |
| } | |
| return null; | |
| } | |
| } | |
| public class OpenBracketToken : IToken { | |
| public int Priority { get { return 10; } } | |
| public static IToken Make(string input, ref int position) { | |
| if(input[position] == '(') { | |
| ++position; | |
| return new OpenBracketToken(); | |
| } | |
| return null; | |
| } | |
| } | |
| public class CloseBracketToken : IToken { | |
| public int Priority { get { return 10; } } | |
| public static IToken Make(string input, ref int position) { | |
| if(input[position] == ')') { | |
| ++position; | |
| return new CloseBracketToken(); | |
| } | |
| return null; | |
| } | |
| } | |
| public class NumberToken : IToken { | |
| public double Value { get; private set; } | |
| public static IToken Make(string input, ref int position) { | |
| if(!char.IsNumber( input[position] )) | |
| return null; | |
| var sb = new StringBuilder(); | |
| bool haveDot = false; | |
| while(position < input.Length) { | |
| char current = input[position]; | |
| if(char.IsNumber( current )) { | |
| sb.Append( current ); | |
| ++position; | |
| continue; | |
| } | |
| if(current == '.') { | |
| if(haveDot) | |
| throw new Exception( "Invalid input at position: " + position ); | |
| sb.Append( current ); | |
| haveDot = true; | |
| ++position; | |
| continue; | |
| } | |
| break; | |
| } | |
| return sb.Length > 0 ? new NumberToken { Value = double.Parse( sb.ToString() ) } : null; | |
| } | |
| } | |
| // -- parser | |
| public interface IExpression { | |
| double Value { get; } | |
| } | |
| public class NumberExpression : IExpression { | |
| public double Value { get; set; } | |
| public override string ToString() { return Value.ToString(); } | |
| } | |
| public class BinaryOperator : IExpression { | |
| public IExpression Left { get; set; } | |
| public IExpression Right { get; set; } | |
| public int Priority { get; set; } | |
| public Func<double, double, double> Make { get; set; } | |
| public double Value { get { return Make( Left.Value, Right.Value ); } } | |
| } | |
| public class UnaryMinusOperator : IExpression { | |
| public IExpression Expression { get; set; } | |
| public double Value { get { return -Expression.Value; } } | |
| } | |
| public class InvalidInputException : Exception { | |
| public InvalidInputException(string expecting, string invalidInput) : | |
| base( string.Format( "Invalid input, expecting {0} got {1}", expecting, invalidInput ) ) { } | |
| } | |
| public class Buffer { | |
| private readonly List<IToken> _tokens; | |
| private int _index; | |
| public Buffer(IEnumerable<IToken> tokens) { | |
| _tokens = new List<IToken>( tokens ); | |
| _index = 0; | |
| } | |
| public bool IsEnd { get { return _tokens.Count <= _index; } } | |
| public bool IsEndOrClosedBracket() { | |
| if(IsEnd) | |
| return true; | |
| if(Current is CloseBracketToken) { | |
| ++_index; | |
| return true; | |
| } | |
| return false; | |
| } | |
| public IToken Current { get { return _index < _tokens.Count ? _tokens[_index] : null; } } | |
| public T GetAs<T>() where T : class { | |
| var ret = Current as T; | |
| if(ret != null) | |
| ++_index; | |
| return ret; | |
| } | |
| } | |
| public static class Parser { | |
| public delegate IToken MakeToken(string input, ref int position); | |
| private static readonly MakeToken[] _makers = new MakeToken[] { | |
| OperatorToken.Make, | |
| OpenBracketToken.Make, | |
| CloseBracketToken.Make, | |
| NumberToken.Make | |
| }; | |
| public static IEnumerable<IToken> GetTokens(string input) { | |
| int position = 0; | |
| while(position < input.Length) { | |
| if(char.IsWhiteSpace( input[position] )) { | |
| ++position; | |
| continue; | |
| } | |
| IToken token = null; | |
| foreach(var maker in _makers) { | |
| token = maker( input, ref position ); | |
| if(token != null) | |
| break; | |
| } | |
| if(token == null) | |
| throw new Exception( "Invalid input at position: " + position ); | |
| yield return token; | |
| } | |
| } | |
| public static IExpression Parse(Buffer buffer) { | |
| return Parse( buffer, 0 ); | |
| } | |
| private static IExpression Parse(Buffer buffer, int bracketLevel) { | |
| if(buffer.IsEnd) { | |
| if(bracketLevel > 0) | |
| throw new Exception( "Unbalanced brackets" ); | |
| return null; | |
| } | |
| var current = GetNumber( buffer, bracketLevel ); | |
| while(!buffer.IsEnd) { | |
| if(bracketLevel > 0 && buffer.GetAs<CloseBracketToken>() != null) { | |
| return current; | |
| } | |
| current = MakeBinaryOperator( current, buffer, bracketLevel ); | |
| } | |
| if(bracketLevel > 0) | |
| throw new Exception( "Unbalanced brackets" ); | |
| return current; | |
| } | |
| private static IExpression GetNumber(Buffer buffer, int bracketLevel) { | |
| if(buffer.IsEnd) | |
| throw new Exception( "Invalid input" ); | |
| var current = buffer.GetAs<OpenBracketToken>(); | |
| if(current != null) { | |
| return Parse( buffer, bracketLevel + 1 ); | |
| } | |
| // check for unary minus | |
| var oper = buffer.GetAs<OperatorToken>(); | |
| if(oper != null) { | |
| if(oper.Operator != '-') | |
| throw new InvalidInputException( "number", oper.ToString() ); | |
| var num = buffer.GetAs<NumberToken>(); | |
| if(num == null) | |
| throw new InvalidInputException( "number", buffer.Current.ToString() ); | |
| return new UnaryMinusOperator { Expression = new NumberExpression { Value = num.Value } }; | |
| } | |
| var numEx = buffer.GetAs<NumberToken>(); | |
| if(numEx == null) | |
| throw new InvalidInputException( "number", buffer.Current.ToString() ); | |
| return new NumberExpression { Value = numEx.Value }; | |
| } | |
| private static IExpression GetRightSide(int prevOperatorPriority, Buffer buffer, int bracketLevel) { | |
| var num = GetNumber( buffer, bracketLevel ); | |
| if(buffer.IsEnd) | |
| return num; | |
| // check the next operator; if the next one is higher priority than the current one, | |
| // return next expression instead of the current number | |
| var next = buffer.Current as IOperatorToken; | |
| if(next != null && next.Priority >= prevOperatorPriority) { | |
| return MakeBinaryOperator( num, buffer, bracketLevel ); | |
| } | |
| return num; | |
| } | |
| private static IExpression MakeBinaryOperator(IExpression leftSide, Buffer buffer, int bracketLevel) { | |
| if(buffer.IsEnd) | |
| throw new Exception( "Invalid input" ); | |
| var oper = buffer.GetAs<OperatorToken>(); | |
| if(oper == null) | |
| throw new InvalidInputException( "operator", buffer.Current.ToString() ); | |
| var right = GetRightSide( oper.Priority, buffer, bracketLevel ); | |
| return new BinaryOperator { Left = leftSide, Right = right, Make = oper.Calc }; | |
| } | |
| } | |
| class Program { | |
| static void Main(string[] args) { | |
| while(true) { | |
| string input; | |
| while(true) { | |
| Console.Write( "> " ); | |
| input = Console.ReadLine().Trim(); | |
| if(!string.IsNullOrEmpty( input )) | |
| break; | |
| } | |
| try { | |
| var tokens = Parser.GetTokens( input ).ToList(); | |
| var buffer = new Buffer( tokens ); | |
| var expression = Parser.Parse( buffer ); | |
| Console.WriteLine( expression.Value ); | |
| } | |
| catch(Exception e) { | |
| Console.WriteLine( e.Message ); | |
| } | |
| } | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment