Permalink
Browse files

First implementation of Shunting yard algorithm

  • Loading branch information...
1 parent 2b68a62 commit 111795780bacb108dfde1119bcef8c9778383c5f @Giorgi committed Feb 27, 2012
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NUnit.Framework;
+
+namespace SimpleExpressionEvaluator.Tests
+{
+ public class ExpressionEvaluatorTests
+ {
+ private ExpressionEvaluator engine;
+ private Random generator;
+
+ [TestFixtureSetUp]
+ public void SetUp()
+ {
+ engine = new ExpressionEvaluator();
+ generator = new Random();
+ }
+
+ [Test]
+ public void Empty_String_Is_Zero()
+ {
+ Assert.That(engine.Evaluate(""), Is.EqualTo(0));
+ }
+
+ [Test]
+ public void Decimal_Is_Treated_As_Decimal()
+ {
+ var left = generator.Next(1, 100);
+
+ Assert.That(engine.Evaluate(left.ToString()), Is.EqualTo(left));
+ }
+
+ [Test]
+ public void Two_Plus_Two_Is_Four()
+ {
+ Assert.That(engine.Evaluate("2+2"), Is.EqualTo(4));
+ }
+
+ [Test]
+ public void Can_Add_Two_Decimal_Numbers()
+ {
+ Assert.That(engine.Evaluate("2.7+3.2"), Is.EqualTo(2.7m + 3.2m));
+ }
+
+ [Test]
+ public void Can_Add_Many_Numbers()
+ {
+ Assert.That(engine.Evaluate("1.2+3.4+5.6+7.8"), Is.EqualTo(1.2m + 3.4m + 5.6m + 7.8m));
+ Assert.That(engine.Evaluate("1.7+2.9+14.24+6.58"), Is.EqualTo(1.7m + 2.9m + 14.24m + 6.58m));
+ }
+
+ [Test]
+ public void Can_Subtract_Two_Numbers()
+ {
+ Assert.That(engine.Evaluate("5-2"), Is.EqualTo(5 - 2));
+ }
+
+ [Test]
+ public void Can_Subtract_Multiple_Numbers()
+ {
+ Assert.That(engine.Evaluate("15.2-2.3-4.8-0.58"), Is.EqualTo(15.2m - 2.3m - 4.8m - 0.58m));
+ }
+
+ [Test]
+ public void Can_Add_And_Subtract_Multiple_Numbers()
+ {
+ Assert.That(engine.Evaluate("15+8-4-2+7"), Is.EqualTo(15 + 8 - 4 - 2 + 7));
+ Assert.That(engine.Evaluate("17.89-2.47+7.16"), Is.EqualTo(17.89m - 2.47m + 7.16m));
+
+ }
+
+ [Test]
+ public void Can_Add_Subtract_Multiply_Divide_Multiple_Numbers()
+ {
+ Assert.That(engine.Evaluate("50-5*3*2+7"), Is.EqualTo(50 - 5 * 3 * 2 + 7));
+ Assert.That(engine.Evaluate("84+15+4-4*3*9+24+4-54/3-5-7+47"), Is.EqualTo(84 + 15 + 4 - 4 * 3 * 9 + 24 + 4 - 54 / 3 - 5 - 7 + 47));
+ Assert.That(engine.Evaluate("50-48/4/3+7*2*4+2+5+8"), Is.EqualTo(50 - 48 / 4 / 3 + 7 * 2 * 4 + 2 + 5 + 8));
+ Assert.That(engine.Evaluate("5/2/2+1.5*3+4.58"), Is.EqualTo(5 / 2m / 2m + 1.5m * 3m + 4.58m));
+ Assert.That(engine.Evaluate("25/3+1.34*2.56+1.49+2.36/1.48"), Is.EqualTo(25 / 3m + 1.34m * 2.56m + 1.49m + 2.36m / 1.48m));
+ Assert.That(engine.Evaluate("2*3+5-4-2*5+7"), Is.EqualTo(2 * 3 + 5 - 4 - 2 * 5 + 7));
+ }
+ }
+}
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SimpleExpressionEvaluator.Tests")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("SimpleExpressionEvaluator.Tests")]
+[assembly: AssemblyCopyright("Copyright © 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("95807a00-691e-4cb4-be31-acb06cacf350")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <PropertyGroup>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+ <ProductVersion>8.0.30703</ProductVersion>
+ <SchemaVersion>2.0</SchemaVersion>
+ <ProjectGuid>{08F4484E-5FD8-4590-A8D7-12FBE47120C8}</ProjectGuid>
+ <OutputType>Library</OutputType>
+ <AppDesignerFolder>Properties</AppDesignerFolder>
+ <RootNamespace>SimpleExpressionEvaluator.Tests</RootNamespace>
+ <AssemblyName>SimpleExpressionEvaluator.Tests</AssemblyName>
+ <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
+ <FileAlignment>512</FileAlignment>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+ <DebugSymbols>true</DebugSymbols>
+ <DebugType>full</DebugType>
+ <Optimize>false</Optimize>
+ <OutputPath>bin\Debug\</OutputPath>
+ <DefineConstants>DEBUG;TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+ <DebugType>pdbonly</DebugType>
+ <Optimize>true</Optimize>
+ <OutputPath>bin\Release\</OutputPath>
+ <DefineConstants>TRACE</DefineConstants>
+ <ErrorReport>prompt</ErrorReport>
+ <WarningLevel>4</WarningLevel>
+ </PropertyGroup>
+ <ItemGroup>
+ <Reference Include="nunit.framework">
+ <HintPath>..\packages\NUnit.2.6.0.12054\lib\nunit.framework.dll</HintPath>
+ </Reference>
+ <Reference Include="System" />
+ <Reference Include="System.Core" />
+ <Reference Include="System.Xml.Linq" />
+ <Reference Include="System.Data.DataSetExtensions" />
+ <Reference Include="Microsoft.CSharp" />
+ <Reference Include="System.Data" />
+ <Reference Include="System.Xml" />
+ </ItemGroup>
+ <ItemGroup>
+ <Compile Include="ExpressionEvaluatorTests.cs" />
+ <Compile Include="Properties\AssemblyInfo.cs" />
+ </ItemGroup>
+ <ItemGroup>
+ <None Include="packages.config" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\SimpleExpressionEvaluator\SimpleExpressionEvaluator.csproj">
+ <Project>{30637214-EDE9-4C2E-BFD6-E4B163FA308B}</Project>
+ <Name>SimpleExpressionEvaluator</Name>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
+ Other similar extension points exist, see Microsoft.Common.targets.
+ <Target Name="BeforeBuild">
+ </Target>
+ <Target Name="AfterBuild">
+ </Target>
+ -->
+</Project>
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+ <package id="NUnit" version="2.6.0.12054" />
+</packages>
@@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleExpressionEvaluator", "SimpleExpressionEvaluator\SimpleExpressionEvaluator.csproj", "{30637214-EDE9-4C2E-BFD6-E4B163FA308B}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleExpressionEvaluator.Tests", "SimpleExpressionEvaluator.Tests\SimpleExpressionEvaluator.Tests.csproj", "{08F4484E-5FD8-4590-A8D7-12FBE47120C8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -13,6 +15,10 @@ Global
{30637214-EDE9-4C2E-BFD6-E4B163FA308B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{30637214-EDE9-4C2E-BFD6-E4B163FA308B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{30637214-EDE9-4C2E-BFD6-E4B163FA308B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {08F4484E-5FD8-4590-A8D7-12FBE47120C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {08F4484E-5FD8-4590-A8D7-12FBE47120C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {08F4484E-5FD8-4590-A8D7-12FBE47120C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {08F4484E-5FD8-4590-A8D7-12FBE47120C8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1,11 +1,166 @@
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using System.IO;
+using System.Linq.Expressions;
namespace SimpleExpressionEvaluator
{
public class ExpressionEvaluator
{
+ public decimal Evaluate(string expression)
+ {
+ if (string.IsNullOrWhiteSpace(expression))
+ {
+ return 0;
+ }
+
+ var expressionStack = new Stack<Expression>();
+ var operatorStack = new Stack<char>();
+
+ using (var reader = new StringReader(expression))
+ {
+ int peek;
+ while ((peek = reader.Peek()) > -1)
+ {
+ var next = (char)peek;
+
+ if (char.IsDigit(next))
+ {
+ expressionStack.Push(ReadOperand(reader));
+ continue;
+ }
+
+ if (Operation.IsDefined(next))
+ {
+ var currentOperation = ReadOperation(reader);
+
+ while (true)
+ {
+ if (operatorStack.Count == 0)
+ {
+ operatorStack.Push(next);
+ break;
+ }
+
+ var lastOperition = operatorStack.Peek();
+
+ if (currentOperation.Precedence > ((Operation)lastOperition).Precedence)
+ {
+ operatorStack.Push(next);
+ break;
+ }
+
+ var right = expressionStack.Pop();
+ var left = expressionStack.Pop();
+
+ expressionStack.Push(((Operation)operatorStack.Pop()).Apply(left, right));
+ }
+ continue;
+ }
+
+ if (next != ' ')
+ {
+ throw new ArgumentException("Invalid character encountered", "expression");
+ }
+ }
+ }
+
+ while (operatorStack.Count > 0)
+ {
+ var right = expressionStack.Pop();
+ var left = expressionStack.Pop();
+
+ expressionStack.Push(((Operation)operatorStack.Pop()).Apply(left, right));
+ }
+
+ var compiled = Expression.Lambda<Func<decimal>>(expressionStack.Pop()).Compile();
+ return compiled();
+ }
+
+ private Expression ReadOperand(StringReader reader)
+ {
+ var operand = string.Empty;
+
+ int peek;
+
+ while ((peek = reader.Peek()) > -1)
+ {
+ var next = (char)peek;
+
+ if (char.IsDigit(next) || next == '.')
+ {
+ reader.Read();
+ operand += next;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return Expression.Constant(string.IsNullOrEmpty(operand) ? decimal.Zero : decimal.Parse(operand));
+ }
+
+ private Operation ReadOperation(TextReader reader)
+ {
+ var operation = (char)reader.Read();
+ return (Operation)operation;
+ }
+ }
+
+ internal sealed class Operation
+ {
+ private readonly int precedence;
+ private readonly string name;
+ private readonly Func<Expression, Expression, Expression> operation;
+
+ 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");
+
+ private static readonly Dictionary<char, Operation> Operations = new Dictionary<char, Operation>
+ {
+ { '+', Addition },
+ { '-', Subtraction },
+ { '*', Multiplication},
+ { '/', Division }
+ };
+
+ private Operation(int precedence, Func<Expression, Expression, Expression> operation, string name)
+ {
+ this.precedence = precedence;
+ this.operation = operation;
+ this.name = name;
+ }
+
+ public int Precedence
+ {
+ get { return precedence; }
+ }
+
+ public static explicit operator Operation(char operation)
+ {
+ Operation result;
+
+ if (Operations.TryGetValue(operation, out result))
+ {
+ return result;
+ }
+ else
+ {
+ throw new InvalidCastException();
+ }
+ }
+
+ public Expression Apply(Expression left, Expression right)
+ {
+ return operation(left, right);
+ }
+
+ public static bool IsDefined(char operation)
+ {
+ return Operations.ContainsKey(operation);
+ }
}
-}
+}
@@ -40,7 +40,7 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
- <Compile Include="Class1.cs" />
+ <Compile Include="ExpressionEvaluator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
Binary file not shown.
Binary file not shown.
Oops, something went wrong.

0 comments on commit 1117957

Please sign in to comment.