Albert Einstein writingSometimes I get strange ideas. "Strange" is perhaps not the right word. In English there is a term for the way a person thinks when handed a task that they know everyone else will solve in one single way. In exactly those moments, a particular way of thinking kicks in for some people, one that tries to find a solution different from the standard one. They call it "thinking outside the box". A few months ago I had to write a program that takes an arithmetic expression as plain text input and then returns its result. Most beginner programmers would head straight for Google, looking for a way to deal with the problem (or, worse still, for a ready-made solution). Good programmers would think of algorithms such as shunting-yard and reverse Polish notation (RPN). And I, being a very mediocre programmer, decided to solve the task in my own mediocre way. I compiled the expression at run time and let the brilliant expression evaluator of the .NET CLR solve the problem for me. Imba, right?

CSharpCodeProvider

I love C#There is a very cool class in .NET. It is called CSharpCodeProvider and it provides functionality for compiling C# code down to the CLR's intermediate language, MSIL. After that, through reflection, you can access its elements (classes, interfaces, methods and so on) and create instances of them. That is exactly what my solution to the task is built on. I create my own code, insert into it the expression I want to evaluate, tell the compiler to compile it for me, and then simply run it. No stacks, no operator precedence, no reverse Polish notation, no state machines, no complicated trees of operations and numbers. Just plain compilation of code in our beloved C#. ;)

Implementation

Lamp ideaNow you will see the code that does the magic described above. First I define an exception that tells me the expression is not correct. You can redefine its constructor, the Message and InnerException properties and so on. I have left it empty, because for the purposes of the demo I do not need the specific error, only the fact that one occurred.

using System;

namespace ExpressionEvaluation
{
    public class IncorrectExpressionException :
        Exception
    {

    }
}

Next comes the class that does the main work. What it does is take the expression as a string, process it, run it through the compiler and then execute it via reflection to get the result we actually need.

using System.CodeDom.Compiler;
using System.Reflection;
using System.Text;
using Microsoft.CSharp;

namespace ExpressionEvaluation
{
    public class ExpressionEvaluator
    {
        private const string codeFormat = @"
        using System;
        namespace ExpressionEvaluation
        {
            public class EvaluatorHelper
            {
                public double Evaluate()
                {
                    return {0};
                }
            }
        }";
        private CSharpCodeProvider cSharpCodeProvider;
        private CompilerParameters cp;

        private string PrepareExpression(string expression)
        {
            StringBuilder expressionBuilder =
                new StringBuilder(expression);
            expressionBuilder.Replace("sqrt", "Math.Sqrt");
            expressionBuilder.Replace("ln", "Math.Log");
            expressionBuilder.Replace("pow", "Math.Pow");
            //return string.Format(codeFormat, expressionBuilder);
            return codeFormat.Replace("{0}",
                expressionBuilder.ToString());
        }

        public ExpressionEvaluator()
        {
            cSharpCodeProvider = new CSharpCodeProvider();
            cp = new CompilerParameters();
            cp.ReferencedAssemblies.Add("system.dll");
            cp.GenerateInMemory = true;
        }

        public bool TryEvaluate(string expression,
            out double result)
        {
            try
            {
                result = this.Evaluate(expression);
                return true;
            }
            catch (IncorrectExpressionException)
            {
                result = 0;
                return false;
            }
        }

        public double Evaluate(string expression)
        {
            try
            {
                expression = this.PrepareExpression(expression);
                CompilerResults compilerResults =
                    cSharpCodeProvider.
                    CompileAssemblyFromSource(cp, expression);
                Assembly assembly =
                    compilerResults.CompiledAssembly;
                object instance = assembly.CreateInstance(
                    "ExpressionEvaluation.EvaluatorHelper");
                object invokeResult = instance.GetType().
                    GetMethod("Evaluate").Invoke(instance, null);
                double result = 0;
                double.TryParse(
                    invokeResult.ToString(), out result);
                return result;
            }
            catch
            {
                throw new IncorrectExpressionException();
            }
        }
    }
}

And finally a console application that shows how the class is used. It reads an expression from the console and prints the resulting value back to the console.

using System;

namespace ExpressionEvaluation
{
    class ExpressionEvaluatorDemo
    {
        static void Main()
        {
            string expression = Console.ReadLine();
            ExpressionEvaluator expressionEvaluator =
                new ExpressionEvaluator();
            double result = 0;
            if (expressionEvaluator.TryEvaluate(expression,
                out result))
            {
                Console.WriteLine(result);
            }
            else
            {
                Console.WriteLine("Incorrect expression!");
            }
        }
    }
}

Conclusion

Monkey thinkingWhat is the point of this post? The point is to show you that when you are given a seemingly simple task with a clearly determined solution, you can always step outside what everyone else will do. Step outside the elementary solution and think up something different, something of your own, something brilliant. My solution to this task is neither the fastest nor the most correct, but it is certainly the shortest and, more importantly, the most different. There is no way to come up with something cool if you do things the way everyone else does, is there? Always aim to solve your tasks in your own unique way, and you will see how, with every passing day, stranger and more interesting ideas come to you. And that matters, if you want to become the next Mark Zuckerberg. ;)