Last active
August 11, 2022 14:24
-
-
Save rpgmaker/4405468 to your computer and use it in GitHub Desktop.
Expression Parser and example.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} | |
} |
You can find it here: https://github.com/rpgmaker/NetJSON/blob/master/NetJSON/AutomaticTypeConverter.cs
Thanks
Are you trying to utilize this for a project?
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
What's the definition for method ToExpectedType? https://gist.github.com/rpgmaker/4405468#file-gistfile1-cs-L167
var obj = value.ToExpectedType();