views:

92

answers:

1

Hey,

Basically I've wrote my own parser and I'm parsing a string into and expression and then compiling and storing to be reused later on.

For (an odd) example the type of string I'm parsing is this:-

if #name == 'max' and #legs > 5 and #ears > 5 then shoot()

The hash parts in the string are telling my parser to look at the properties on the object of type T that I pass in, if not found to look in a Dictionary that also gets passed in as extra.

I parse up the string into parts create an expression and join the expressions using methods from the PredicateBuilder.

I get the left value from where ever it may be and then the right value and turn it into an int32 or a string based on if it's wrapped in single quotes.. for now, then pass both into Expression.Equals/Expression.GreaterThan etc. dependant on the operator.

So far this works fine for equals and strings but take for example #ears which isn't a property on the object but is in the dictionary I end up with "does string equal int32" and it throws an exception.

I need to be able to parse the string from the dictionary into and int32 and then try the equal/greater/less than method.

Hopefully someone could shed some light on this as I haven't found anything yet that will do it and its driving me mad.

Here is the CreateExpression method I'm using to create an expression from the string.

    private Expression<Func<T, IDictionary<string, string>, bool>> CreateExpression<T>(string condition)
    {
        string[] parts = condition.Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);

        if (parts.Length == 3)
        {
            var typeCache = _cache[typeof(T).FullName];
            var param = Expression.Parameter(typeCache.T, "o");
            var param2 = Expression.Parameter(typeof(IDictionary<string, string>), "d");

            /*  Convert right hand side into correct value type
             */

            Expression right = null;

            if (parts[2][0] == '\'' && parts[2][parts[2].Length - 1] == '\'')
                right = Expression.Constant(parts[2].Trim(new char[] { '\'' }), typeof(string));
            else
            {
                int x = 0;
                right = (int.TryParse(parts[2].Trim(), out x))
                    ? Expression.Constant(x)
                    : Expression.Constant(parts[2].Trim(new char[] { '\'' }), typeof(string));
            }

            /* Get the left hand side value from object T or IDictionary and then attempt to convert it
             * into the right hand side value type
             */

            Expression left = null;

            var key = (parts[0][0] == '#') ? parts[0].TrimStart('#') : parts[0];

            if (_cache[typeCache.T.FullName].Properties.Find(key, true) == null)
            {
                var m = typeof(ExpressionExtensions).GetMethod("GetValue");

                left = Expression.Call(null, m, new Expression[] { param2, Expression.Constant(key) });
            }
            else
                left = Expression.PropertyOrField(param, key);

            /* Find the operator and return the correct expression
             */

            if (parts[1] == "==")
            {
                return Expression.Lambda<Func<T, IDictionary<string, string>, bool>>(
                                Expression.Equal(left, right), new ParameterExpression[] { param, param2 });
            }
            else if (parts[1] == ">")
            {
                return Expression.Lambda<Func<T, IDictionary<string, string>, bool>>(
                                Expression.GreaterThan(left, right), new ParameterExpression[] { param, param2 });
            }
        }

        return null;
    }

And here is the method to parse the whole string into parts and build up the expression.

public Func<T, IDictionary<string, string>, bool> Parse<T>(string rule)
    {
        var type = typeof(T);
        if (!_cache.ContainsKey(type.FullName))
            _cache[type.FullName] = new TypeCacheItem()
            {
                T = type,
                Properties = TypeDescriptor.GetProperties(type)
            };

        Expression<Func<T, IDictionary<string, string>, bool>> exp = null;

        var actionIndex = rule.IndexOf("then");
        if (rule.IndexOf("if") == 0 && actionIndex > 0)
        {
            var conditionStatement = rule.Substring(2, actionIndex - 2).Trim();
            var actionStatement = rule.Substring(actionIndex + 4).Trim();

            var startIndex = 0;
            var endIndex = 0;
            var conditionalOperator = "";
            var lastConditionalOperator = "";
            do
            {
                endIndex = FindConditionalOperator(conditionStatement, out conditionalOperator, startIndex);

                var condition = (endIndex == -1) ? conditionStatement.Substring(startIndex) : conditionStatement.Substring(startIndex, endIndex - startIndex);

                var x = CreateExpression<T>(condition.Trim());
                if (x != null)
                {
                    if (exp != null)
                    {
                        switch (lastConditionalOperator)
                        {
                            case "or":
                                exp = exp.Or<T>(x);
                                break;
                            case "and":
                                exp = exp.And<T>(x);
                                break;
                            default:
                                exp = x;
                                break;
                        }
                    }
                    else
                        exp = x;
                }

                lastConditionalOperator = conditionalOperator;

                startIndex = endIndex + conditionalOperator.Length;
            } while (endIndex > -1);
        }
        else
            throw new ArgumentException("Rule must start with 'if' and contain 'then'.");

        return (exp == null) ? null : exp.Compile();
    }

My eyes are open to suggestions or advice on this but the idea of parsing that string as is to return true or false has to be,

Richard.

EDIT:

I've now alter my method that retrieves the value from the dictionary to a generic one as Fahad suggested to this:-

public static T GetValue<T>(this IDictionary<string, string> dictionary, string key)
    {
        string x = string.Empty;
        dictionary.TryGetValue(key, out x);

        if (typeof(T) == typeof(int))
        {
            int i = 0;
            if (int.TryParse(x, out i))
                return (T)(i as object);
        }

        return default(T);
        //throw new ArgumentException("Failed to convert dictionary value to type.");
    }

And this works fine but I would rather return null than a default value for the type i've tried using Nullable ... where T : struct and returning null when not found or can't convert but I don't know how to use the null result in my expression.

A: 

I think you should redo your problem analysis and try another way. One way I would propose is to have a generic method that would give you the value from a IDictionary or IList etc., and use this method to wrap around your Expression's. The Expression would only want the "Type" of the object to be satisfied, if that is done correctly, then you could easily do it. If you know the "Type" then you can implement it with generic methods, otherwise, you could still use reflection and do generics.

So all you have to think is how to get the value from the dictionary or any other list through expressions.

Hope that helps.

-Fahad

Fahad
Hi, I use this extension method to retrieve a value from the dictionary public static string GetValue(this IDictionary<string, string> dictionary, string key) { string x = string.Empty; dictionary.TryGetValue(key, out x); return x; }Maybe I should alter that to return the type I want?
Richard Adnams