Skip to content

Instantly share code, notes, and snippets.

@apetrovic
Created July 14, 2014 20:34
Show Gist options
  • Select an option

  • Save apetrovic/aa0f411b5dcb1451c9db to your computer and use it in GitHub Desktop.

Select an option

Save apetrovic/aa0f411b5dcb1451c9db to your computer and use it in GitHub Desktop.
Simple calculator in C#
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