Skip to content

Instantly share code, notes, and snippets.

@rpgmaker
Last active August 11, 2022 14:24
Show Gist options
  • Save rpgmaker/4405468 to your computer and use it in GitHub Desktop.
Save rpgmaker/4405468 to your computer and use it in GitHub Desktop.
Expression Parser and example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Collections;
namespace ExpressionParser {
public class Parser {
static readonly Regex _keyRegex = new Regex(@"\s*(?<Key>[^$3]+)\b\s*(?<Oper>=|!=|<=|>=|>|<|like)\s*(?<Value>.+)(\s*(=|!=|<=|>=|>|<|like))??",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
static readonly Regex _containRegex = new Regex(@"(.+)(.Contains\()("".+"")(\))(.+)",
RegexOptions.Compiled | RegexOptions.Multiline | RegexOptions.IgnoreCase);
static readonly ConcurrentDictionary<string, Func<Expression, Expression, Expression>> _cachedExpression =
new ConcurrentDictionary<string, Func<Expression, Expression, Expression>>();
static readonly ConcurrentDictionary<string, Delegate> _cachedCompiledExpression =
new ConcurrentDictionary<string, Delegate>();
static Func<Expression, Expression, Expression> GetOperatorExpression(string oper) {
oper = oper.ToLower();
return _cachedExpression.ContainsKey(oper) ? _cachedExpression[oper] :
new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.Empty());
}
static Parser() {
_cachedExpression["="] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.Equal(ex1, ex2));
_cachedExpression["!="] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.NotEqual(ex1, ex2));
_cachedExpression["<"] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.LessThan(ex1, ex2));
_cachedExpression["<="] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.LessThanOrEqual(ex1, ex2));
_cachedExpression[">"] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.GreaterThan(ex1, ex2));
_cachedExpression[">="] = new Func<Expression, Expression, Expression>((ex1, ex2) => Expression.GreaterThanOrEqual(ex1, ex2));
_cachedExpression["like"] = new Func<Expression, Expression, Expression>((ex1, ex2) =>
{
return Expression.Call(ex1, typeof(string).GetMethod("Contains", new[] { typeof(string) }), ex2);
});
}
private Dictionary<string, ParameterExpression> _parameters;
private string _expression;
private int _index;
private List<object> _list = new List<object>();
public Parser(string expression) {
Reset(expression);
}
private Expression Parse(List<object> list) {
var body = default(Expression);
for (var i = 0; i < list.Count; i++) {
var operand = list[i] as string;
if (operand == null) {
var result = Parse(list[i]);
if (body == null) body = result;
else continue;
} else {
if (operand == "||") body = Expression.Or(body, Parse(list[i + 1]));
else body = Expression.And(body, Parse(list[i + 1]));
i += 1;
}
}
return body;
}
private Expression Parse(object obj) {
return obj is Expression ? obj as Expression : Parse(obj as List<object>);
}
public void Reset(string expression) {
_expression = expression;
if (_parameters == null)
_parameters = new Dictionary<string, ParameterExpression>();
else _parameters.Clear();
_index = 0;
}
private ParameterExpression GetParameter(string key, Type type) {
if (_parameters.ContainsKey(key)) return _parameters[key];
return _parameters[key] = Expression.Parameter(type, key);
}
public Delegate Build() {
if (String.IsNullOrWhiteSpace(_expression)) return null;
return _cachedCompiledExpression.GetOrAdd(_expression, exp =>
{
Process(_expression, ref _index, _list);
var parameters = _parameters.OrderBy(x => x.Key).Select(x => x.Value).ToList();
var lambda = Expression.Lambda(Parse(_list), parameters);
return lambda.Compile();
});
}
public static Delegate Build(string expression) {
return new Parser(expression).Build();
}
private static Tuple<string, string> ReadToOperand(string expression, ref int index) {
var sb = new StringBuilder();
var operand = string.Empty;
bool isOr = false, isAnd = false;
var length = expression.Length;
while (index < length) {
sb.Append(expression[index++]);
var hasOp = sb.Length >= 2 &&
((isAnd = (sb[sb.Length - 2] == '&' && sb[sb.Length - 1] == '&')) ||
(isOr = (sb[sb.Length - 2] == '|' && sb[sb.Length - 1] == '|')));
if (hasOp) break;
}
return new Tuple<string, string>((isOr || isAnd) ? sb.ToString().Substring(0, sb.Length - 2) : sb.ToString(),
isOr ? "||" : isAnd ? "&&" : null);
}
private static string ReadToBrace(string expression, ref int index, string startExp, string delimeter = "") {
var count = Math.Max(startExp.Count(x => x == '(') - startExp.Count(x => x == ')'), 0);
var chr = '\0';
var sb = new StringBuilder(startExp + delimeter);
var length = expression.Length;
while (count > 0 && index < length) {
sb.Append(chr = expression[index++]);
if (chr == '(') count++;
else if (chr == ')') count--;
}
return sb.ToString().Trim();
}
private void Process(string expression, ref int index, List<object> list) {
if (index >= expression.Length) return;
var tuple = ReadToOperand(expression, ref index);
var exp = tuple.Item1;
var operand = tuple.Item2;
if (exp.Contains("(")) {
var items = new List<object>();
var create = false;
list.Add(items);
var expr = ReadToBrace(expression, ref index, exp, operand);
var idx = 0;
if (expr.StartsWith("(") && expr.EndsWith(")"))
expr = expr.Substring(1, expr.Length - 2);
else if(expr.StartsWith("(") && (expr.EndsWith("||") || expr.EndsWith("&&"))){
exp = exp.Trim();
Create(exp.Substring(1, exp.Length - 2), operand, list);
create = true;
}
if (!create) {
Process(expr, ref idx, items);
tuple = ReadToOperand(expression, ref index);
if (!String.IsNullOrWhiteSpace(tuple.Item2)) list.Add(tuple.Item2);
}
} else if (!String.IsNullOrWhiteSpace(exp)) {
Create(exp, operand, list);
}
Process(expression, ref index, list);
}
private void Create(string expression, string operand, List<object> list) {
expression = expression.Trim();
var match = _keyRegex.Match(expression);
var key = match.Groups["Key"].Value.Trim();
var value = match.Groups["Value"].Value.Trim();
if (value.StartsWith("\"") && value.EndsWith("\""))
value = value.Substring(1, value.Length - 2);
var obj = value.ToExpectedType();
var oper = match.Groups["Oper"].Value;
var type = obj.GetType();
var result = GetOperatorExpression(oper)(GetParameter(key, type),
Expression.Constant(obj, type));
list.Add(result);
if (operand != null) list.Add(operand);
}
private static object Normalize(object value) {
if (typeof(bool) == value.GetType()) return value.ToString().ToLower();
return value;
}
private static string GetOperator(ExpressionType type) {
return type == ExpressionType.And || type == ExpressionType.AndAlso ? " && " :
type == ExpressionType.Or || type == ExpressionType.OrElse ? " || " :
type == ExpressionType.GreaterThan ? " > " :
type == ExpressionType.GreaterThanOrEqual ? " >= " :
type == ExpressionType.LessThan ? " < " :
type == ExpressionType.LessThanOrEqual ? " <= " :
type == ExpressionType.Equal ? " = " :
type == ExpressionType.NotEqual ? " != " :
type == ExpressionType.Call ? "like" :
string.Empty;
}
private static void WalkExpression(Expression expression, StringBuilder builder) {
var binary = expression as BinaryExpression;
if (binary != null) {
builder.Append("(");
WalkExpression(binary.Left, builder);
builder.Append(GetOperator(binary.NodeType));
WalkExpression(binary.Right, builder);
builder.Append(")");
} else {
var field = expression as MemberExpression;
var methodCall = expression as MethodCallExpression;
if (!(field == null && methodCall == null)) {
if (expression.ToString().Contains("_DisplayClass"))
builder.Append(Normalize(Expression.Lambda(expression).Compile().DynamicInvoke()));
else builder.Append(expression.ToString());
} else {
var constant = expression as ConstantExpression;
if (constant != null) builder.Append(Normalize(constant.Value));
else {
var parameter = expression as ParameterExpression;
if (parameter != null) builder.Append(parameter.Name);
}
}
}
}
public static string ExpressionToFilter<TMessage>(Expression<Func<TMessage, bool>> expression) where TMessage : class {
var sb = new StringBuilder();
var param = expression.Parameters[0].Name;
WalkExpression(expression.Body, sb);
var result = sb.ToString().Replace(param + ".", string.Empty);
while (result.Contains("Contains"))
result = _containRegex.Replace(result, "$1 like $3$4$5");
return result;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Linq.Expressions;
using System.Collections.Concurrent;
namespace ExpressionParser {
public class Test {
public int a { get; set; }
public int b { get; set; }
public string c { get; set; }
}
class Program {
static void Main(string[] args) {
var exp = "((((((a < 20) || (a = 10)) || ((b > 10) && ((b < 30) || (b < 40)))) || (b = 5)) || c like \"jo\"))";//"a < 20 || a = 10 || (b > 10 && (b < 30 || b < 40)) || b = 5 || c like \"john\" || c like \"joke,\" || c = \"Test\"";
var func = Parser.Build(exp);
var result = func.DynamicInvoke(100, 40, "joke,");
var tree = Parser.ExpressionToFilter<Test>(x => x.a < 20 || x.a == 10 || (x.b > 10 && (x.b < 30 || x.b < 40)) || x.b == 5 || x.c.Contains("jo") || x.c.Contains("joke"));
Console.WriteLine("Result: {0}", result);
Console.WriteLine("Expression: {0}", tree);
}
}
}
@chucklu
Copy link

chucklu commented Sep 24, 2019

What's the definition for method ToExpectedType? https://gist.github.com/rpgmaker/4405468#file-gistfile1-cs-L167
var obj = value.ToExpectedType();

@rpgmaker
Copy link
Author

@chucklu
Copy link

chucklu commented Sep 25, 2019

Thanks

@rpgmaker
Copy link
Author

Are you trying to utilize this for a project?

@chucklu
Copy link

chucklu commented Sep 25, 2019

public class User
    {
        public int Id { get; set; }
        public bool Active { get; set; }
    }

  [Test]
        public void TestMemberExpression()
        {
            UserHelper<User>.Fetch(x => x.Active && x.Id == 2);
        }

  public static User Fetch(Expression<Func<User, object>> predicate)
        {
            Console.WriteLine(predicate.GetType());
            Console.WriteLine(predicate.Body);
            return null;
        }

I would to parse the above expression into key value array or list:
key:Active value:true
key:Id value:2

The official site provide demo Interpreting Expressions
But it did not contains a demo for ExpressionType.Convert

 public static User Fetch(Expression<Func<User, object>> predicate)
        {
            Visitor visitor = new LambdaVisitor(predicate);
            visitor.Visit("Chuckk--");
            return null;
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment