This project has moved and is read-only. For the latest updates, please go here.

Usage of C# Expression Evaluator.

May 5, 2014 at 7:31 AM
Guys,

I am evaluating this component to evaluate similar kind of expressions in one of my project. The only difference is that the expressions are not that straight forward and includes custom types and methods vs using premitive data types.

Here is the sample expression that I need to evaluate. This is very small and the expressions can be nested up to any level. Nesting is not the concern here but tweaking the source code to use custom object definitely is.

@ATADJ( @MAX( @SUBTR(@PR( 987043 ) , @AMT( 913000 ) ) , @MULT( @PR( 987043 ) , 0.20 ) ) , 60 )

@ATADJ - This can be considered as a method which would take float value and would return float value after doing some adjustment.
@SUBTR - Substititute for subtract.

Similarily there would be plenty of such methods which would be member of a utility class and each performing specific function.

I know there is concept called TypeRegistry to register external types but slightly struggling on how that can be used to evaluate the expresison like above. Quick pointers on the classes that I'd need to modify to evaluate the above expression would be very helpful.

Need help and guidance in taking the key decisions before I go too far into the R&D if using this component would be helpful here.
May 5, 2014 at 9:17 AM
Edited May 5, 2014 at 9:28 AM
Hi kumarvinodsha,

This can be done is a roundabout way, without having to modify any classes. First of all, you could preprocess the expression into something that will resemble method calls. If you're not too picky a simple replace "@" with "obj." might work.

obj.ATADJ( obj.MAX( obj.SUBTR(obj.PR( 987043 ) , obj.AMT( 913000 ) ) , obj.MULT( obj.PR( 987043 ) , 0.20 ) ) , 60 )

You then make your utility class with the corresponding methods. That class should probably have ALL the necessary methods that might be called in your expression. The TypeRegistry is where you register your utility class so that obj is evaluated in the expression to return your utility class instance.

The only issue I would see with this is what other tokens are involved aside from @methodname, or how else is the @ sign used if you are doing a simple replace.

There are also very very slight performance issues with wrapping basic operators like subtract and multiply in a function because instead of directly acting on the values you pass them into a function, execute the function and get the return value. However, these performance issues are probably trivial compared to the rest of the expression which I assume are also implemented as functions anyway.

Of course, if you want to rewrite the parser to be specific to your "language" you would have to change the grammar (g3) file almost completely.

But unless it is a complex feature-rich language that absolutely necessitates it's own parser, you probably would be better of doing some preprocessing.

You could make your utility class:

    public class Utility
    {
        public float ATADJ(float value, float adj)
        {
            return value;  // assuming that I did something with the value
        }

        public float MAX(float value1, float value2)
        {
            return new List<float> { value1, value2 }.Max();
        }

        public float MAX(params float[] value)
        {
            return value.Max();
        }

        public float SUBTR(float left, float right)
        {
            return left - right;
        }

        public float MULT(float left, float right)
        {
            return left * right;
        }

        public float PR(float value)
        {
            return value;  // don't know what this does
        }

        public float AMT(float value)
        {
            return value; // don't know what this does
        }
    }
Then in your application:
            var exp = "@ATADJ( @MAX( @SUBTR(@PR( 987043 ) , @AMT( 913000 ) ) , @MULT( @PR( 987043 ) , 0.20f ) ) , 60f ) ";
            var util = new Utility();
            var reg = new TypeRegistry();
            reg.RegisterSymbol("util", util);
            exp = exp.Replace("@", "util.");
            var ce = new CompiledExpression() { StringToParse = exp, TypeRegistry = reg };
            var res = ce.Eval();
With the symbol "util" being used to refer to you utility class, you cannot use it anymore anywhere in your original expression, though I doubt that will be a big problem.

Note that all parameters were implemented using float.

It would be good to know the full range of expressions involved to know what workarounds can be done to cater to them.
May 5, 2014 at 3:28 PM
____Thanks a lot for such a super fast response RupertAvery! I must appreciate you guys have been doing great job! The way you understood my problem statement and created the sample progrem is awsome!

I tried your suggested approach but seems like the source code I am using is different from the one which you built the sample program on. Because The method reg.RegisterSymbol doesn't exist in my TypeRegistry class. In my TypeRegistry class I have constructor and one method RegisterDefaultTypes(). So to register my Utility class there I added this.Add("Utility",typeof(Utility)); into the RegisterDefaultTypes() method and called that from constructor. I did this to achive what i belive you were trying to achieve using reg.RegisterSymbol("util", util);. But I may be wrong as its just my assumption.

I tested the sample program for simple expression
var exp = "@SUBTR( 200,100)" I changed the float type to Int throught in SUBTR method.

var exp = "@SUBTR( 200,100)";
        var Utility = new Utility();
        var reg = new TypeRegistry(); //within this my Utility type is getting registered. 
        exp = exp.Replace("@", "Utility.");
        var ce = new CompiledExpression() { StringToParse = exp, TypeRegistry = reg };

        var res = ce.Eval();     
But its throwing error in public static Expression MemberAccess at line return Expression.Call(instance, methodInfo, args.ToArray()); as the instance is null.

If this is because of the difference in code version then please point me to the latest code. I pulled code from here https://csharpeval.codeplex.com/SourceControl/latest#README.md
May 5, 2014 at 5:15 PM
Hi,

Make sure you have the AntlrParser branch selected. You should have the file csharpeval-39bb174e029613a89c8f4b68bb80119ff46c584c.zip

The sample code I wrote should already be in the Tests Project in Program.cs

If you're not yet familiar with Git, I strongly suggest you install GitExtensions (https://code.google.com/p/gitextensions/) so you can easily get the latest codes.

If all you need is the binary, make sure to get the 2.0.1 version.




May 6, 2014 at 12:02 PM
Thanks for your response RupertAvery!

I downloaded 39bb174e029613a89c8f4b68bb80119ff46c584c.zip
I tried building the code but hitting couple of errors. Here are the steps that I followed:

1 I am using Visual Studio 2010 so the solution file directly doesn't work for me. So I manually added all the folders/ files into a new project.

2 Added the reference for Antlr3.Runtime.dll.

I got the build errors listed below.

1 Error 2 'ExpressionEvaluator.Parser.ExprEvalParser.ReportError(Antlr.Runtime.RecognitionException)': no suitable method found to override.

at line public override void ReportError(RecognitionException e). We are overriding method here but not driving class <public partial class ExprEvalParser> from any base class.

Similarily hitting the error at line base.ReportError(e); for the same above reason.

2 In the same class getting error <The name 'input' does not exist in the current contex> at line throw new ExpressionParseException(message, input); the input variable is no where declared.

Seems like the code I pulled has some pieces missing. Further help on this would be appreciated.
May 6, 2014 at 1:49 PM

Have you tried just using the precompiled binary fronthe downloads page? I will try to make a visual studio 2010 solution. I think you might be missing the ANTLR generated files.

Since you set it up manually you might have to set the compile action for the g3 file.

May 6, 2014 at 5:14 PM
Edited May 6, 2014 at 5:15 PM
Hi,

I have added a Visual Studio 2010 solution.

I simply added the existing project to VS2010 and it worked out of the box, no other adjustments needed.

Only the library and Test program are included, unit tests are not included since I used VS2010 Express which doesn't support unit test projects.

As usual make sure the AntlrParser branch is selected when you download the latest source.
May 8, 2014 at 6:16 PM
This worked like magic! Thanks a lot for all your support!