Skip to content

Commit

Permalink
Added support for unary minus. Fixes #3
Browse files Browse the repository at this point in the history
  • Loading branch information
Giorgi committed Sep 27, 2015
1 parent b89666c commit ba6d406
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 12 deletions.
18 changes: 18 additions & 0 deletions SimpleExpressionEvaluator.Tests/ExpressionEvaluatorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -141,5 +141,23 @@ public void Can_Invoke_Expression_Multiple_Times()

Assert.That(compiled(new { a, b, c }), Is.EqualTo((a + b) / (a + c)));
}

[Test]
public void NegativeNumber_Parsed_AsNegativeNumber()
{
var left = -generator.Next(1, 100);

Assert.That(engine.Evaluate(left.ToString()), Is.EqualTo(left));
}

[Test]
public void Can_EvaluateExpression_WithNegativeNumbers()
{
Assert.That(engine.Evaluate("-5 + 3"), Is.EqualTo(-5 + 3));
Assert.That(engine.Evaluate("5 + (-3)"), Is.EqualTo(5 + (-3)));

Assert.That(engine.Evaluate("5 + (4-3)"), Is.EqualTo(5 + (4 - 3)));
Assert.That(engine.Evaluate("5 + (-(4-3))"), Is.EqualTo(5 + (-(4 - 3))));
}
}
}
93 changes: 81 additions & 12 deletions SimpleExpressionEvaluator/ExpressionEvaluator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace SimpleExpressionEvaluator
public class ExpressionEvaluator : DynamicObject
{
private readonly Stack<Expression> expressionStack = new Stack<Expression>();
private readonly Stack<char> operatorStack = new Stack<char>();
private readonly Stack<Symbol> operatorStack = new Stack<Symbol>();
private readonly List<string> parameters = new List<string>();


Expand Down Expand Up @@ -104,26 +104,40 @@ public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, o

if (Operation.IsDefined(next))
{
if (next == '-' && expressionStack.Count == 0)
{
reader.Read();
operatorStack.Push(Operation.UnaryMinus);
continue;
}

var currentOperation = ReadOperation(reader);

EvaluateWhile(() => operatorStack.Count > 0 && operatorStack.Peek() != '(' &&
EvaluateWhile(() => operatorStack.Count > 0 && operatorStack.Peek() != Parentheses.Left &&
currentOperation.Precedence <= ((Operation)operatorStack.Peek()).Precedence);

operatorStack.Push(next);
operatorStack.Push(currentOperation);
continue;
}

if (next == '(')
{
reader.Read();
operatorStack.Push('(');
operatorStack.Push(Parentheses.Left);

if (reader.Peek() == '-')
{
reader.Read();
operatorStack.Push(Operation.UnaryMinus);
}

continue;
}

if (next == ')')
{
reader.Read();
EvaluateWhile(() => operatorStack.Count > 0 && operatorStack.Peek() != '(');
EvaluateWhile(() => operatorStack.Count > 0 && operatorStack.Peek() != Parentheses.Left);
operatorStack.Pop();
continue;
}
Expand Down Expand Up @@ -199,10 +213,15 @@ private void EvaluateWhile(Func<bool> condition)
{
while (condition())
{
var right = expressionStack.Pop();
var left = expressionStack.Pop();
var operation = (Operation)operatorStack.Pop();

var expressions = new Expression[operation.NumberOfOperands];
for (var i = operation.NumberOfOperands - 1; i >= 0; i--)
{
expressions[i] = expressionStack.Pop();
}

expressionStack.Push(((Operation)operatorStack.Pop()).Apply(left, right));
expressionStack.Push(operation.Apply(expressions));
}
}

Expand Down Expand Up @@ -287,16 +306,18 @@ private bool IsNumeric(Type type)
}
}

internal sealed class Operation
internal sealed class Operation : Symbol
{
private readonly int precedence;
private readonly string name;
private readonly Func<Expression, Expression, Expression> operation;
private readonly Func<Expression, Expression> unaryOperation;

public static readonly Operation Addition = new Operation(1, Expression.Add, "Addition");
public static readonly Operation Subtraction = new Operation(1, Expression.Subtract, "Subtraction");
public static readonly Operation Multiplication = new Operation(2, Expression.Multiply, "Multiplication");
public static readonly Operation Division = new Operation(2, Expression.Divide, "Division");
public static readonly Operation UnaryMinus = new Operation(2, Expression.Negate, "Division");

private static readonly Dictionary<char, Operation> Operations = new Dictionary<char, Operation>
{
Expand All @@ -306,13 +327,26 @@ internal sealed class Operation
{ '/', Division }
};

private Operation(int precedence, Func<Expression, Expression, Expression> operation, string name)
private Operation(int precedence, string name)
{
this.name = name;
this.precedence = precedence;
}

private Operation(int precedence, Func<Expression, Expression> unaryOperation, string name) : this(precedence, name)
{
this.unaryOperation = unaryOperation;
NumberOfOperands = 1;
}

private Operation(int precedence, Func<Expression, Expression, Expression> operation, string name) : this(precedence, name)
{
this.operation = operation;
this.name = name;
NumberOfOperands = 2;
}

public int NumberOfOperands { get; private set; }

public int Precedence
{
get { return precedence; }
Expand All @@ -332,7 +366,12 @@ public int Precedence
}
}

public Expression Apply(Expression left, Expression right)
private Expression Apply(Expression expression)
{
return unaryOperation(expression);
}

private Expression Apply(Expression left, Expression right)
{
return operation(left, right);
}
Expand All @@ -341,5 +380,35 @@ public static bool IsDefined(char operation)
{
return Operations.ContainsKey(operation);
}

public Expression Apply(params Expression[] expressions)
{
if (expressions.Length == 1)
{
return Apply(expressions[0]);
}

if (expressions.Length == 2)
{
return Apply(expressions[0], expressions[1]);
}

throw new NotImplementedException();
}
}

internal class Parentheses : Symbol
{
public static readonly Parentheses Left = new Parentheses();
public static readonly Parentheses Right = new Parentheses();

private Parentheses()
{

}
}

internal class Symbol
{
}
}

0 comments on commit ba6d406

Please sign in to comment.