Linq 표현 식 트 리 분석 1

원문:http://www.codeproject.com/Articles/355513/Invent-your-own-Dynamic-LINQ-parser
LINQ Expression Tree
LINQ Expression Tree: http://msdn.microsoft.com/en-us/library/bb397951.aspx
LINQ Expressions: http://msdn.microsoft.com/en-us/library/system.linq.expressions.aspx
Expression Factory: http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx
Dynamic LINQ
ScottGu's Blog http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
핵심 코드:
테스트 코드:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace SimpleExpression
{
    public abstract class PredicateParser
    {
        #region scanner
        /// tokenizer pattern: Optional-SpaceS...Token...Optional-Spaces
        private static readonly string _pattern = @"\s*(" + string.Join("|", new string[]
          {
              // operators and punctuation that are longer than one char: longest first
              string.Join("|", new string[] { "||", "&&", "==", "!=", "<=", ">=" }.Select(e => Regex.Escape(e))),
              @"""(?:\\.|[^""])*""",  // string
              @"\d+(?:\.\d+)?",       // number with optional decimal part
              @"\w+",                 // word
              @"\S",                  // other 1-char tokens (or eat up one character in case of an error)
          }) + @")\s*";
        /// get 1st char of current token (or a Space if no 1st char is obtained)
        private char Ch { get { return string.IsNullOrEmpty(Curr) ? ' ' : Curr[0]; } }
        /// move one token aheadtrue = moved ahead, false = end of stream
        private bool Move() { return _tokens.MoveNext(); }
        /// the token stream implemented as IEnumerator
        private IEnumerator _tokens;
        /// constructs the scanner for the given input string
        protected PredicateParser(string s)
        {
            _tokens = Regex.Matches(s, _pattern, RegexOptions.Compiled).Cast()
                      .Select(m => m.Groups[1].Value).GetEnumerator();
            Move();
        }
        protected bool IsNumber { get { return char.IsNumber(Ch); } }
        protected bool IsDouble { get { return IsNumber && Curr.Contains('.'); } }
        protected bool IsString { get { return Ch == '"'; } }
        protected bool IsIdent { get { char c = Ch; return char.IsLower(c) || char.IsUpper(c) || c == '_'; } }
        /// throw an argument exception
        protected void Abort(string msg) { throw new ArgumentException("Error: " + (msg ?? "unknown error")); }
        /// get the current item of the stream or an empty string after the end
        protected string Curr { get { return _tokens.Current ?? string.Empty; } }
        /// get current and move to the next token (error if at end of stream)
        protected string CurrAndNext { get { string s = Curr; if (!Move()) Abort("data expected"); return s; } }
        /// get current and move to the next token if available
        protected string CurrOptNext { get { string s = Curr; Move(); return s; } }
        /// moves forward if current token matches and returns that (next token must exist)
        protected string CurrOpAndNext(params string[] ops)
        {
            string s = ops.Contains(Curr) ? Curr : null;
            if (s != null && !Move()) Abort("data expected");
            return s;
        }
        #endregion
    }
    public class PredicateParser : PredicateParser
    {
        #region code generator

        private static readonly Type _bool = typeof(bool);
        private static readonly Type[] _prom = new Type[]
        { 
            typeof(decimal), typeof(double), typeof(float), 
            typeof(ulong), typeof(long), typeof(uint),
            typeof(int), typeof(ushort), typeof(char),
            typeof(short), typeof(byte), typeof(sbyte)
        };

        /// enforce the type on the expression (by a cast) if not already of that type
        private static Expression Coerce(Expression expr, Type type)
        {
            return expr.Type == type ? expr : Expression.Convert(expr, type);
        }

        /// casts if needed the expr to the "largest" type of both arguments
        private static Expression Coerce(Expression expr, Expression sibling)
        {
            if (expr.Type != sibling.Type)
            {
                Type maxType = MaxType(expr.Type, sibling.Type);
                if (maxType != expr.Type) expr = Expression.Convert(expr, maxType);
            }
            return expr;
        }

        /// returns the first if both are same, or the largest type of both (or the first)
        private static Type MaxType(Type a, Type b)
        {
            return a == b ? a : (_prom.FirstOrDefault(t => t == a || t == b) ?? a);
        }
        /// 
        /// Code generation of binary and unary epressions, utilizing type coercion where needed
        /// 
        private static readonly Dictionary> _binOp =
        new Dictionary>()
        {
            { "||", (a,b)=>Expression.OrElse(Coerce(a, _bool), Coerce(b, _bool)) },
            { "&&", (a,b)=>Expression.AndAlso(Coerce(a, _bool), Coerce(b, _bool)) },
            { "==", (a,b)=>Expression.Equal(Coerce(a,b), Coerce(b,a)) },
            { "!=", (a,b)=>Expression.NotEqual(Coerce(a,b), Coerce(b,a)) },
            { "Expression.LessThan(Coerce(a,b), Coerce(b,a)) },
            { "<=", (a,b)=>Expression.LessThanOrEqual(Coerce(a,b), Coerce(b,a)) },
            { ">=", (a,b)=>Expression.GreaterThanOrEqual(Coerce(a,b), Coerce(b,a)) },
            { ">", (a,b)=>Expression.GreaterThan(Coerce(a,b), Coerce(b,a)) },
        };

        private static readonly Dictionary> _unOp =
        new Dictionary>()
        {
            { "!", a=>Expression.Not(Coerce(a, _bool)) },
        };

        /// create a constant of a value
        private static ConstantExpression Const(object v) { return Expression.Constant(v); }
        /// create lambda parameter field or property access
        private MemberExpression ParameterMember(string s) { return Expression.PropertyOrField(_param, s); }
        /// create lambda expression
        private Expression> Lambda(Expression expr) { return Expression.Lambda>(expr, _param); }
        /// the lambda's parameter (all names are members of this)
        private readonly ParameterExpression _param = Expression.Parameter(typeof(TData), "_p_");
        #endregion

        #region parser
        /// initialize the parser (and thus, the scanner)
        private PredicateParser(string s) : base(s) { }
        /// main entry point
        public static Expression> Parse(string s) { return new PredicateParser(s).Parse(); }
        private Expression> Parse() { return Lambda(ParseExpression()); }
        private Expression ParseExpression() { return ParseOr(); }
        private Expression ParseOr() { return ParseBinary(ParseAnd, "||"); }
        private Expression ParseAnd() { return ParseBinary(ParseEquality, "&&"); }
        private Expression ParseEquality() { return ParseBinary(ParseRelation, "==", "!="); }
        private Expression ParseRelation() { return ParseBinary(ParseUnary, "=", ">"); }
        private Expression ParseUnary()
        {
            return CurrOpAndNext("!") != null ? _unOp["!"](ParseUnary()) : ParsePrimary();
        }
        //private Expression ParseIdent() { return ParameterMember(CurrOptNext); } //     
        private Expression ParseIdent() //      
        {
            Expression expr = ParameterMember(CurrOptNext);
            while (CurrOpAndNext(".") != null && IsIdent) expr = Expression.PropertyOrField(expr, CurrOptNext);
            return expr;
        }
        private Expression ParseString()
        {
            return Const(Regex.Replace(CurrOptNext, "^\"(.*)\"$", m => m.Groups[1].Value));
        }
        private Expression ParseNumber()
        {
            if (IsDouble) return Const(double.Parse(CurrOptNext));
            return Const(int.Parse(CurrOptNext));
        }
        private Expression ParsePrimary()
        {
            if (IsIdent) return ParseIdent();
            if (IsString) return ParseString();
            if (IsNumber) return ParseNumber();
            return ParseNested();
        }
        private Expression ParseNested()
        {
            if (CurrAndNext != "(") Abort("(...) expected");
            Expression expr = ParseExpression();
            if (CurrOptNext != ")") Abort("')' expected");
            return expr;
        }
        /// generic parsing of binary expressions
        private Expression ParseBinary(Func parse, params string[] ops)
        {
            Expression expr = parse();
            string op;
            while ((op = CurrOpAndNext(ops)) != null) expr = _binOp[op](expr, parse());
            return expr;
        }
        #endregion
    }
}

테스트 코드:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace SimpleExpression
{
    public abstract class PredicateParser
    {
        #region scanner
        /// tokenizer pattern: Optional-SpaceS...Token...Optional-Spaces
        private static readonly string _pattern = @"\s*(" + string.Join("|", new string[]
          {
              // operators and punctuation that are longer than one char: longest first
              string.Join("|", new string[] { "||", "&&", "==", "!=", "<=", ">=" }.Select(e => Regex.Escape(e))),
              @"""(?:\\.|[^""])*""",  // string
              @"\d+(?:\.\d+)?",       // number with optional decimal part
              @"\w+",                 // word
              @"\S",                  // other 1-char tokens (or eat up one character in case of an error)
          }) + @")\s*";
        /// get 1st char of current token (or a Space if no 1st char is obtained)
        private char Ch { get { return string.IsNullOrEmpty(Curr) ? ' ' : Curr[0]; } }
        /// move one token aheadtrue = moved ahead, false = end of stream
        private bool Move() { return _tokens.MoveNext(); }
        /// the token stream implemented as IEnumerator
        private IEnumerator _tokens;
        /// constructs the scanner for the given input string
        protected PredicateParser(string s)
        {
            _tokens = Regex.Matches(s, _pattern, RegexOptions.Compiled)
                        .Cast()
                        .Select(m => m.Groups[1].Value)
                        .GetEnumerator();
            Move();
        }
        protected bool IsNumber { get { return char.IsNumber(Ch); } }
        protected bool IsDouble { get { return IsNumber && Curr.Contains('.'); } }
        protected bool IsString { get { return Ch == '"'; } }
        protected bool IsIdent { get { char c = Ch; return char.IsLower(c) || char.IsUpper(c) || c == '_'; } }
        /// throw an argument exception
        protected void Abort(string msg) { throw new ArgumentException("Error: " + (msg ?? "unknown error")); }
        /// get the current item of the stream or an empty string after the end
        protected string Curr { get { return _tokens.Current ?? string.Empty; } }
        /// get current and move to the next token (error if at end of stream)
        protected string CurrAndNext { get { string s = Curr; if (!Move()) Abort("data expected"); return s; } }
        /// get current and move to the next token if available
        protected string CurrOptNext { get { string s = Curr; Move(); return s; } }
        /// moves forward if current token matches and returns that (next token must exist)
        protected string CurrOpAndNext(params string[] ops)
        {
            string s = ops.Contains(Curr) ? Curr : null;
            if (s != null && !Move()) Abort("data expected");
            return s;
        }
        #endregion
    }
    public class PredicateParser : PredicateParser
    {
        #region code generator
        private static readonly Expression _zero = Expression.Constant(0); //add
        private static readonly Type _bool = typeof(bool);
        private static readonly Type[] _prom = new Type[]
        { 
            typeof(decimal), typeof(double), typeof(float), 
            typeof(ulong), typeof(long), typeof(uint),
            typeof(int), typeof(ushort), typeof(char),
            typeof(short), typeof(byte), typeof(sbyte)
        };

        /// enforce the type on the expression (by a cast) if not already of that type
        private static Expression Coerce(Expression expr, Type type)
        {
            return expr.Type == type ? expr : Expression.Convert(expr, type);
        }

        /// casts if needed the expr to the "largest" type of both arguments
        private static Expression Coerce(Expression expr, Expression sibling)
        {
            if (expr.Type != sibling.Type)
            {
                Type maxType = MaxType(expr.Type, sibling.Type);
                if (maxType != expr.Type) expr = Expression.Convert(expr, maxType);
            }
            return expr;
        }

        /// returns the first if both are same, or the largest type of both (or the first)
        private static Type MaxType(Type a, Type b)
        {
            return a == b ? a : (_prom.FirstOrDefault(t => t == a || t == b) ?? a);
        }

        /// produce comparison based on IComparable types
        private static Expression CompareToExpression(Expression lhs, Expression rhs, Func rel)
        {
            lhs = Coerce(lhs, rhs);
            rhs = Coerce(rhs, lhs);
            Expression cmp = Expression.Call(
                lhs,
                lhs.Type.GetMethod("CompareTo", new Type[] { rhs.Type })
                    ?? lhs.Type.GetMethod("CompareTo", new Type[] { typeof(object) }),
                rhs
            );
            return rel(cmp);
        }

        /// 
        /// Code generation of binary and unary epressions, utilizing type coercion where needed
        /// 
        private static readonly Dictionary> _binOp =
        new Dictionary>()
        {
            { "||", (a,b)=>Expression.OrElse(Coerce(a, _bool), Coerce(b, _bool)) },
            { "&&", (a,b)=>Expression.AndAlso(Coerce(a, _bool), Coerce(b, _bool)) },
            //{ "==", (a,b)=>Expression.Equal(Coerce(a,b), Coerce(b,a)) },
            //{ "!=", (a,b)=>Expression.NotEqual(Coerce(a,b), Coerce(b,a)) },
            //{ "Expression.LessThan(Coerce(a,b), Coerce(b,a)) },
            //{ "<=", (a,b)=>Expression.LessThanOrEqual(Coerce(a,b), Coerce(b,a)) },
            //{ ">=", (a,b)=>Expression.GreaterThanOrEqual(Coerce(a,b), Coerce(b,a)) },
            //{ ">", (a,b)=>Expression.GreaterThan(Coerce(a,b), Coerce(b,a)) },
            //Replace to=>
            { "==", (a,b)=>CompareToExpression(a, b, c=>Expression.Equal             (c, _zero)) },
            { "!=", (a,b)=>CompareToExpression(a, b, c=>Expression.NotEqual          (c, _zero)) },
            { "CompareToExpression(a, b, c=>Expression.LessThan          (c, _zero)) },
            { "<=", (a,b)=>CompareToExpression(a, b, c=>Expression.LessThanOrEqual   (c, _zero)) },
            { ">=", (a,b)=>CompareToExpression(a, b, c=>Expression.GreaterThanOrEqual(c, _zero)) },
            { ">",  (a,b)=>CompareToExpression(a, b, c=>Expression.GreaterThan       (c, _zero)) },    
            //To extend the parser=>
            { "+", (a,b)=>Expression.Add(Coerce(a,b), Coerce(b,a)) },
            { "-", (a,b)=>Expression.Subtract(Coerce(a,b), Coerce(b,a)) },
            { "*", (a,b)=>Expression.Multiply(Coerce(a,b), Coerce(b,a)) },
            { "/", (a,b)=>Expression.Divide(Coerce(a,b), Coerce(b,a)) },
            { "%", (a,b)=>Expression.Modulo(Coerce(a,b), Coerce(b,a)) },
        };

        private static readonly Dictionary> _unOp =
        new Dictionary>()
        {
            { "!", a=>Expression.Not(Coerce(a, _bool)) },
            //To extend the parser=>
            { "-", a=>Expression.Negate(a) },
        };

        /// create a constant of a value
        private static ConstantExpression Const(object v) { return Expression.Constant(v); }
        /// create lambda parameter field or property access
        private MemberExpression ParameterMember(string s) { return Expression.PropertyOrField(_param, s); }
        /// create lambda expression
        private Expression> Lambda(Expression expr) { return Expression.Lambda>(expr, _param); }
        /// the lambda's parameter (all names are members of this)
        private readonly ParameterExpression _param = Expression.Parameter(typeof(TData), "_p_");
        #endregion

        #region parser
        /// initialize the parser (and thus, the scanner)
        private PredicateParser(string s) : base(s) { }
        /// main entry point
        public static Expression> Parse(string s) { return new PredicateParser(s).Parse(); }
        private Expression> Parse() { return Lambda(ParseExpression()); }
        private Expression ParseExpression() { return ParseOr(); }
        private Expression ParseOr() { return ParseBinary(ParseAnd, "||"); }
        private Expression ParseAnd() { return ParseBinary(ParseEquality, "&&"); }
        private Expression ParseEquality() { return ParseBinary(ParseRelation, "==", "!="); }
        //private Expression ParseRelation() { return ParseBinary(ParseUnary, "=", ">"); }
        private Expression ParseRelation() { return ParseBinary(ParseSum, "=", ">"); }
        private Expression ParseSum() { return ParseBinary(ParseMul, "+", "-"); }
        private Expression ParseMul() { return ParseBinary(ParseUnary, "*", "/", "%"); }

        private Expression ParseUnary()
        {
            if (CurrOpAndNext("!") != null) return _unOp["!"](ParseUnary());
            if (CurrOpAndNext("-") != null) return _unOp["-"](ParseUnary());
            return ParsePrimary();
            //return CurrOpAndNext("!") != null ? _unOp["!"](ParseUnary()) : ParsePrimary();
        }
        //private Expression ParseIdent() { return ParameterMember(CurrOptNext); } //     
        private Expression ParseIdent() //      
        {
            Expression expr = ParameterMember(CurrOptNext);
            while (CurrOpAndNext(".") != null && IsIdent) expr = Expression.PropertyOrField(expr, CurrOptNext);
            return expr;
        }
        private Expression ParseString()
        {
            return Const(Regex.Replace(CurrOptNext, "^\"(.*)\"$", m => m.Groups[1].Value));
        }
        private Expression ParseNumber()
        {
            if (IsDouble) return Const(double.Parse(CurrOptNext));
            return Const(int.Parse(CurrOptNext));
        }
        private Expression ParsePrimary()
        {
            if (IsIdent) return ParseIdent();
            if (IsString) return ParseString();
            if (IsNumber) return ParseNumber();
            return ParseNested();
        }
        private Expression ParseNested()
        {
            if (CurrAndNext != "(") Abort("(...) expected");
            Expression expr = ParseExpression();
            if (CurrOptNext != ")") Abort("')' expected");
            return expr;
        }
        /// generic parsing of binary expressions
        private Expression ParseBinary(Func parse, params string[] ops)
        {
            Expression expr = parse();
            string op;
            while ((op = CurrOpAndNext(ops)) != null) expr = _binOp[op](expr, parse());
            return expr;
        }
        #endregion
    }
}

출력 결과:
Linq表达式树解析1_第1张图片
using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqExpTest
{
    /// 
    /// Linq        
    /// 
    class Program
    {
        static void Main(string[] args)
        {
            var items = new List()
            {
#region
                //new Element("a", 1000,new List{new CC{email="fdsf@fd",age=12}}),
                //new Element("b", 900,new List{new CC{email="fdsf@fd",age=12}}),
                //new Element("c", 800,new List{new CC{email="fdsf@fd",age=12}}),
                //new Element("d", 700,new List{new CC{email="fdsf@fd",age=12}}),
                //new Element("e", 600,new List{new CC{email="fdsf@fd",age=12}}),
                //new Element("x", 500,new List{new CC{email="fdsf@fd",age=12}}),
                //new Element("y", 400,new List{new CC{email="fdsf@fd",age=12}}),
                //new Element("z", 300,new List{new CC{email="fdsf@fd",age=12}})
#endregion
                new Element("a", 1000,new CC{email="fdsf@fd",age=12}),
                new Element("b", 900,new CC{email="fdsf@fd",age=17}),
                new Element("c", 800,new CC{email="fdsf@fd",age=25}),
                new Element("d", 700,new CC{email="fdsf@fd",age=8}),
                new Element("e", 600,new CC{email="fdsf@fd",age=9}),
                new Element("x", 500,new CC{email="fdsf@fd",age=35}),
                new Element("y", 400,new CC{email="fdsf@fd",age=23}),
                new Element("z", 900,new CC{email="fdsf@fd",age=16})
            };

            //string s = "Name == \"x\" || Number >= 800 && PCC.age % 2==0 && PCC.age>15 && PCC.age*2<35";
            string s = "Name == \"x\" || Number >= 800 && PCC.age>15 && PCC.age*2<35";
            var pred = SimpleExpression.PredicateParser.Parse(s);
            Console.WriteLine("User Entry: {0}", s);
            Console.WriteLine("Expr Tree:  {0}", pred.ToString());

            var f = pred.Compile();
            Console.WriteLine("\r
==============mark affected items=============="); foreach (var item in items) { Console.WriteLine("{2} Name = {0}, Number = {1}, CC: email={3}, age={4}" , item.Name, item.Number, f(item) ? "x" : " ", item.PCC.email, item.PCC.age); } Console.WriteLine("==============where-select=============="); var q = from e in items where f(e) select e; foreach (var item in q) { Console.WriteLine(" Name = {0}, Number = {1},CC: email={2}, age={3}", item.Name, item.Number, item.PCC.email, item.PCC.age); } Console.ReadKey(); } } class Element { private string _name; private int _number; private CC _cc; //private IList _listcc; //public Element(string name, int number, List listcc) public Element(string name, int number, CC cc) { this._name = name; this._number = number; this._cc = cc; //this._listcc = listcc; } public string Name { get { return _name; } set { _name = value; } } public int Number { get { return _number; } set { _number = value; } } public CC PCC { get { return _cc; } set { _cc = value; } } //public IList ListCC { get { return _listcc; } set { _listcc = value; } } } class CC { public string email { get; set; } public int age { get; set; } } }

Linq表达式树解析1_第2张图片
원문 아래 에 이 방법 에 대한 확장 설명 을 제시 했다.
Add more operations
To extend the parser: You may easily add more to the expressions, especially new operators is a simple thing:
to add  +  and  -  binary operators, add them to the  _binOp  dictionary (similar to  == , e.g. , ( "+": Expression.Add(...)"-":   Expression.Subtract(...) ) create  ParseSum()  as a copy of ParseRelation , pass  "+", "-"  as ops, pass  ParseSum  to  ParseRelation  (in place of the  ParseUnary ), pass  ParseUnary  to  ParseSum . That's it.
likewise for  "*", "/", "%" : make  ParseMul  as copy of the above mentioned  ParseSum , pass the right ParseXXX  actions, add the respective Expression factories to the  _binOps  dictionary. Done.
An unary  "-"  is to be added in the  _unOps  dictionary (no coercion needed). The parsing is done in the ParseUnary()  function, e.g.
......

좋은 웹페이지 즐겨찾기