Browse Source

commit: use TokenParser before replace keyword

Local 4 năm trước cách đây
mục cha
commit
536c6e251a

+ 200 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/ClassFactory.cs

@@ -0,0 +1,200 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Threading;
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal class ClassFactory
+    {
+        public static readonly ClassFactory Instance = new ClassFactory();
+
+        static ClassFactory()
+        {
+        }  // Trigger lazy initialization of static fields
+
+        private readonly ModuleBuilder _module;
+        private readonly Dictionary<Signature, Type> _classes;
+        private int _classCount;
+        private readonly ReaderWriterLock _rwLock;
+
+        private ClassFactory()
+        {
+            var name = new AssemblyName("DynamicClasses");
+            var assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run);
+#if ENABLE_LINQ_PARTIAL_TRUST
+            new ReflectionPermission(PermissionState.Unrestricted).Assert();
+#endif
+            try
+            {
+                _module = assembly.DefineDynamicModule("Module");
+            }
+            // ReSharper disable once RedundantEmptyFinallyBlock
+            finally
+            {
+#if ENABLE_LINQ_PARTIAL_TRUST
+                PermissionSet.RevertAssert();
+#endif
+            }
+            _classes = new Dictionary<Signature, Type>();
+            _rwLock = new ReaderWriterLock();
+        }
+
+        public Type GetDynamicClass(IEnumerable<DynamicProperty> properties)
+        {
+            _rwLock.AcquireReaderLock(Timeout.Infinite);
+            try
+            {
+                var signature = new Signature(properties);
+                if (!_classes.TryGetValue(signature, out var type))
+                {
+                    type = CreateAndCacheDynamicClass(signature);
+                }
+                return type;
+            }
+            finally
+            {
+                _rwLock.ReleaseReaderLock();
+            }
+        }
+
+        private Type CreateAndCacheDynamicClass(Signature signature)
+        {
+            var cookie = _rwLock.UpgradeToWriterLock(Timeout.Infinite);
+            try
+            {
+                if (!_classes.TryGetValue(signature, out var type))
+                {
+                    type = CreateDynamicClass(signature.Properties);
+                    _classes.Add(signature, type);
+                }
+
+                return type;
+            }
+            finally
+            {
+                _rwLock.DowngradeFromWriterLock(ref cookie);
+            }
+        }
+
+        private Type CreateDynamicClass(DynamicProperty[] properties)
+        {
+            var typeName = "DynamicClass" + (_classCount + 1);
+#if ENABLE_LINQ_PARTIAL_TRUST
+            new ReflectionPermission(PermissionState.Unrestricted).Assert();
+#endif
+            try
+            {
+                var tb = _module.DefineType(typeName, TypeAttributes.Class |
+                                                      TypeAttributes.Public, typeof(DynamicClass));
+                var fields = GenerateProperties(tb, properties);
+                GenerateEquals(tb, fields);
+                GenerateGetHashCode(tb, fields);
+                var result = tb.CreateType();
+                _classCount++;
+                return result;
+            }
+            // ReSharper disable once RedundantEmptyFinallyBlock
+            finally
+            {
+#if ENABLE_LINQ_PARTIAL_TRUST
+                PermissionSet.RevertAssert();
+#endif
+            }
+        }
+
+        private FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties)
+        {
+            FieldInfo[] fields = new FieldBuilder[properties.Length];
+            for (var i = 0; i < properties.Length; i++)
+            {
+                var dp = properties[i];
+                var fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private);
+                var pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null);
+                var mbGet = tb.DefineMethod("get_" + dp.Name,
+                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
+                    dp.Type, Type.EmptyTypes);
+                var genGet = mbGet.GetILGenerator();
+                genGet.Emit(OpCodes.Ldarg_0);
+                genGet.Emit(OpCodes.Ldfld, fb);
+                genGet.Emit(OpCodes.Ret);
+                var mbSet = tb.DefineMethod("set_" + dp.Name,
+                    MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
+                    null, new[] { dp.Type });
+                var genSet = mbSet.GetILGenerator();
+                genSet.Emit(OpCodes.Ldarg_0);
+                genSet.Emit(OpCodes.Ldarg_1);
+                genSet.Emit(OpCodes.Stfld, fb);
+                genSet.Emit(OpCodes.Ret);
+                pb.SetGetMethod(mbGet);
+                pb.SetSetMethod(mbSet);
+                fields[i] = fb;
+            }
+            return fields;
+        }
+
+        private void GenerateEquals(TypeBuilder tb, FieldInfo[] fields)
+        {
+            var mb = tb.DefineMethod("Equals",
+                MethodAttributes.Public | MethodAttributes.ReuseSlot |
+                MethodAttributes.Virtual | MethodAttributes.HideBySig,
+                typeof(bool), new[] { typeof(object) });
+            var gen = mb.GetILGenerator();
+            var other = gen.DeclareLocal(tb);
+            var next = gen.DefineLabel();
+            gen.Emit(OpCodes.Ldarg_1);
+            gen.Emit(OpCodes.Isinst, tb);
+            gen.Emit(OpCodes.Stloc, other);
+            gen.Emit(OpCodes.Ldloc, other);
+            gen.Emit(OpCodes.Brtrue_S, next);
+            gen.Emit(OpCodes.Ldc_I4_0);
+            gen.Emit(OpCodes.Ret);
+            gen.MarkLabel(next);
+            foreach (var field in fields)
+            {
+                var ft = field.FieldType;
+                var ct = typeof(EqualityComparer<>).MakeGenericType(ft);
+                next = gen.DefineLabel();
+                // ReSharper disable once AssignNullToNotNullAttribute
+                gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);
+                gen.Emit(OpCodes.Ldarg_0);
+                gen.Emit(OpCodes.Ldfld, field);
+                gen.Emit(OpCodes.Ldloc, other);
+                gen.Emit(OpCodes.Ldfld, field);
+                // ReSharper disable once AssignNullToNotNullAttribute
+                gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new[] { ft, ft }), null);
+                gen.Emit(OpCodes.Brtrue_S, next);
+                gen.Emit(OpCodes.Ldc_I4_0);
+                gen.Emit(OpCodes.Ret);
+                gen.MarkLabel(next);
+            }
+            gen.Emit(OpCodes.Ldc_I4_1);
+            gen.Emit(OpCodes.Ret);
+        }
+
+        // ReSharper disable once ParameterTypeCanBeEnumerable.Local
+        private static void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields)
+        {
+            var mb = tb.DefineMethod("GetHashCode",
+                MethodAttributes.Public | MethodAttributes.ReuseSlot |
+                MethodAttributes.Virtual | MethodAttributes.HideBySig,
+                typeof(int), Type.EmptyTypes);
+            var gen = mb.GetILGenerator();
+            gen.Emit(OpCodes.Ldc_I4_0);
+            foreach (var field in fields)
+            {
+                var ft = field.FieldType;
+                var ct = typeof(EqualityComparer<>).MakeGenericType(ft);
+                // ReSharper disable once AssignNullToNotNullAttribute
+                gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null);
+                gen.Emit(OpCodes.Ldarg_0);
+                gen.Emit(OpCodes.Ldfld, field);
+                // ReSharper disable once AssignNullToNotNullAttribute
+                gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new[] { ft }), null);
+                gen.Emit(OpCodes.Xor);
+            }
+            gen.Emit(OpCodes.Ret);
+        }
+    }
+}

+ 24 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/DynamicClass.cs

@@ -0,0 +1,24 @@
+using System.Reflection;
+using System.Text;
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal abstract class DynamicClass
+    {
+        public override string ToString()
+        {
+            var props = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
+            var sb = new StringBuilder();
+            sb.Append("{");
+            for (var i = 0; i < props.Length; i++)
+            {
+                if (i > 0) sb.Append(", ");
+                sb.Append(props[i].Name);
+                sb.Append("=");
+                sb.Append(props[i].GetValue(this, null));
+            }
+            sb.Append("}");
+            return sb.ToString();
+        }
+    }
+}

+ 53 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/DynamicExpression.cs

@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq.Expressions;
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal static class DynamicExpression
+    {
+        public static Expression Parse(ParameterExpression[] parameters, Type resultType, string expression, params object[] values)
+        {
+            var parser = new ExpressionParser(parameters, expression, values);
+            return parser.Parse(resultType);
+        }
+
+        public static Expression Parse(Type resultType, string expression, params object[] values)
+        {
+            var parser = new ExpressionParser(null, expression, values);
+            return parser.Parse(resultType);
+        }
+
+        public static LambdaExpression ParseLambda(Type itType, Type resultType, string expression, params object[] values)
+        {
+            return ParseLambda(new[] { Expression.Parameter(itType, "") }, resultType, expression, values);
+        }
+
+        public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, params object[] values)
+        {
+            var parser = new ExpressionParser(parameters, expression, values);
+            return Expression.Lambda(parser.Parse(resultType), parameters);
+        }
+
+        public static LambdaExpression ParseLambda(Type delegateType, ParameterExpression[] parameters, Type resultType, string expression, params object[] values)
+        {
+            var parser = new ExpressionParser(parameters, expression, values);
+            return Expression.Lambda(delegateType, parser.Parse(resultType), parameters);
+        }
+
+        public static Expression<Func<T, TS>> ParseLambda<T, TS>(string expression, params object[] values)
+        {
+            return (Expression<Func<T, TS>>)ParseLambda(typeof(T), typeof(TS), expression, values);
+        }
+
+        public static Type CreateClass(params DynamicProperty[] properties)
+        {
+            return ClassFactory.Instance.GetDynamicClass(properties);
+        }
+
+        public static Type CreateClass(IEnumerable<DynamicProperty> properties)
+        {
+            return ClassFactory.Instance.GetDynamicClass(properties);
+        }
+    }
+}

+ 10 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/DynamicOrdering.cs

@@ -0,0 +1,10 @@
+using System.Linq.Expressions;
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal class DynamicOrdering
+    {
+        public Expression Selector;
+        public bool Ascending;
+    }
+}

+ 18 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/DynamicProperty.cs

@@ -0,0 +1,18 @@
+using System;
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal class DynamicProperty
+    {
+        public DynamicProperty(string name, Type type)
+        {
+            if (type == null) throw new ArgumentNullException(nameof(type));
+            Name = name ?? throw new ArgumentNullException(nameof(name));
+            Type = type;
+        }
+
+        public string Name { get; }
+
+        public Type Type { get; }
+    }
+}

+ 264 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/DynamicQueryable.cs

@@ -0,0 +1,264 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+
+// ReSharper disable MemberCanBePrivate.Global
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    /// <summary>
+    /// Microsoft provided class. It allows dynamic string based querying.
+    /// Very handy when, at compile time, you don't know the type of queries that will be generated.
+    /// </summary>
+    internal static class DynamicQueryable
+    {
+        #region IQueryable Extensions
+
+        public static IQueryable<T> Where<T>(this IQueryable<T> source, string predicate, params object[] values)
+        {
+            return (IQueryable<T>)Where((IQueryable)source, predicate, values);
+        }
+
+        public static IQueryable Where(this IQueryable source, string predicate, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            if (predicate == null) throw new ArgumentNullException(nameof(predicate));
+            var lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values);
+            return source.Provider.CreateQuery(
+                Expression.Call(
+                    typeof(Queryable), "Where",
+                    new[] { source.ElementType },
+                    source.Expression, Expression.Quote(lambda)));
+        }
+
+        public static IQueryable Select(this IQueryable source, string selector, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            if (selector == null) throw new ArgumentNullException(nameof(selector));
+            var lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
+            return source.Provider.CreateQuery(
+                Expression.Call(
+                    typeof(Queryable), "Select",
+                    new[] { source.ElementType, lambda.Body.Type },
+                    source.Expression, Expression.Quote(lambda)));
+        }
+
+        public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, params object[] values)
+        {
+            return (IQueryable<T>)OrderBy((IQueryable)source, ordering, values);
+        }
+
+        public static IQueryable OrderBy(this IQueryable source, string ordering, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            if (ordering == null) throw new ArgumentNullException(nameof(ordering));
+            var parameters = new[] {
+                Expression.Parameter(source.ElementType, "") };
+            var parser = new ExpressionParser(parameters, ordering, values);
+            var orderings = parser.ParseOrdering();
+            var queryExpr = source.Expression;
+            var methodAsc = "OrderBy";
+            var methodDesc = "OrderByDescending";
+            foreach (var o in orderings)
+            {
+                queryExpr = Expression.Call(
+                    typeof(Queryable), o.Ascending ? methodAsc : methodDesc,
+                    new[] { source.ElementType, o.Selector.Type },
+                    queryExpr, Expression.Quote(Expression.Lambda(o.Selector, parameters)));
+                methodAsc = "ThenBy";
+                methodDesc = "ThenByDescending";
+            }
+            return source.Provider.CreateQuery(queryExpr);
+        }
+
+        public static IQueryable Take(this IQueryable source, int count)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.Provider.CreateQuery(
+                Expression.Call(
+                    typeof(Queryable), "Take",
+                    new[] { source.ElementType },
+                    source.Expression, Expression.Constant(count)));
+        }
+
+        public static IQueryable Skip(this IQueryable source, int count)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.Provider.CreateQuery(
+                Expression.Call(
+                    typeof(Queryable), "Skip",
+                    new[] { source.ElementType },
+                    source.Expression, Expression.Constant(count)));
+        }
+
+        public static IQueryable GroupBy(this IQueryable source, string keySelector, string elementSelector, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            if (keySelector == null) throw new ArgumentNullException(nameof(keySelector));
+            if (elementSelector == null) throw new ArgumentNullException(nameof(elementSelector));
+            var keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values);
+            var elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values);
+            return source.Provider.CreateQuery(
+                Expression.Call(
+                    typeof(Queryable), "GroupBy",
+                    new[] { source.ElementType, keyLambda.Body.Type, elementLambda.Body.Type },
+                    source.Expression, Expression.Quote(keyLambda), Expression.Quote(elementLambda)));
+        }
+
+        public static bool Any(this IQueryable source)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return (bool)source.Provider.Execute(
+                Expression.Call(
+                    typeof(Queryable), "Any",
+                    new[] { source.ElementType }, source.Expression));
+        }
+
+        public static int Count(this IQueryable source)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return (int)source.Provider.Execute(
+                Expression.Call(
+                    typeof(Queryable), "Count",
+                    new[] { source.ElementType }, source.Expression));
+        }
+
+        public static IQueryable Distinct(this IQueryable source)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.Provider.CreateQuery(
+                Expression.Call(
+                    typeof(Queryable), "Distinct",
+                    new[] { source.ElementType },
+                    source.Expression));
+        }
+
+        /// <summary>
+        /// Dynamically runs an aggregate function on the IQueryable.
+        /// </summary>
+        /// <param name="source">The IQueryable data source.</param>
+        /// <param name="function">The name of the function to run. Can be Sum, Average, Min, Max.</param>
+        /// <param name="member">The name of the property to aggregate over.</param>
+        /// <returns>The value of the aggregate function run over the specified property.</returns>
+        public static object Aggregate(this IQueryable source, string function, string member)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            if (member == null) throw new ArgumentNullException(nameof(member));
+
+            // Properties
+            var property = source.ElementType.GetProperty(member) ?? throw new ArgumentException($"Property [{member}] not found", nameof(member));
+            var parameter = Expression.Parameter(source.ElementType, "s");
+            Expression selector = Expression.Lambda(Expression.MakeMemberAccess(parameter, property), parameter);
+            // We've tried to find an expression of the type Expression<Func<TSource, TAcc>>,
+            // which is expressed as ( (TSource s) => s.Price );
+
+            //var methods = typeof(Queryable).GetMethods().Where(x => x.Name == function);
+
+            // Method
+            var aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
+                m => m.Name == function
+                    && m.ReturnType == property.PropertyType // should match the type of the property
+                    && m.IsGenericMethod);
+
+            // Sum, Average
+            if (aggregateMethod != null)
+            {
+                return source.Provider.Execute(
+                    Expression.Call(
+                        null,
+                        aggregateMethod.MakeGenericMethod(source.ElementType),
+                        new[] { source.Expression, Expression.Quote(selector) }));
+            }
+            // Min, Max
+            else
+            {
+                aggregateMethod = typeof(Queryable).GetMethods().SingleOrDefault(
+                    m => m.Name == function
+                        && m.GetGenericArguments().Length == 2
+                        && m.IsGenericMethod);
+
+                if (null == aggregateMethod) throw new ArgumentException();
+
+                return source.Provider.Execute(
+                    Expression.Call(
+                        null,
+                        aggregateMethod.MakeGenericMethod(source.ElementType, property.PropertyType),
+                        new[] { source.Expression, Expression.Quote(selector) }));
+            }
+        }
+
+        #endregion IQueryable Extensions
+
+        #region IEnumerable Extensions
+
+        public static IEnumerable<T> Where<T>(this IEnumerable<T> source, string predicate, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().Where(predicate, values);
+        }
+
+        public static IEnumerable Where(this IEnumerable source, string predicate, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().Where(predicate, values);
+        }
+
+        public static IEnumerable Select(this IEnumerable source, string selector, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().Select(selector, values);
+        }
+
+        public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> source, string ordering, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().OrderBy(ordering, values);
+        }
+
+        public static IEnumerable OrderBy(this IEnumerable source, string ordering, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().OrderBy(ordering, values);
+        }
+
+        public static IEnumerable Take(this IEnumerable source, int count)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().Take(count);
+        }
+
+        public static IEnumerable Skip(this IEnumerable source, int count)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().Skip(count);
+        }
+
+        public static IEnumerable GroupBy(this IEnumerable source, string keySelector, string elementSelector, params object[] values)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().GroupBy(keySelector, elementSelector, values);
+        }
+
+        public static bool Any(this IEnumerable source)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().Any();
+        }
+
+        public static int Count(this IEnumerable source)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().Count();
+        }
+
+        public static IEnumerable Distinct(this IEnumerable source)
+        {
+            if (source == null) throw new ArgumentNullException(nameof(source));
+            return source.AsQueryable().Distinct();
+        }
+
+        #endregion IEnumerable Extensions
+    }
+}

Những thai đổi đã bị hủy bỏ vì nó quá lớn
+ 61 - 1015
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq.cs


+ 343 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/ExpressionParserBase.cs

@@ -0,0 +1,343 @@
+using System;
+using System.Globalization;
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal class ExpressionParserBase
+    {
+        public struct Token
+        {
+            public TokenId Id;
+            public string Text;
+            public int Pos;
+        }
+
+        public enum TokenId
+        {
+            // ReSharper disable once UnusedMember.Global
+            Unknown,
+
+            End,
+            Identifier,
+            StringLiteral,
+            IntegerLiteral,
+            RealLiteral,
+            Exclamation,
+            Percent,
+            Amphersand,
+            OpenParen,
+            CloseParen,
+            Asterisk,
+            Plus,
+            Comma,
+            Minus,
+            Dot,
+            Slash,
+            Colon,
+            LessThan,
+            Equal,
+            GreaterThan,
+            Question,
+            OpenBracket,
+            CloseBracket,
+            Bar,
+            ExclamationEqual,
+            DoubleAmphersand,
+            LessThanEqual,
+            LessGreater,
+            DoubleEqual,
+            GreaterThanEqual,
+            DoubleBar
+        }
+
+        private readonly string _text;
+        private char _ch;
+
+        protected int TextPos;
+        protected readonly int TextLen;
+        protected Token CurrentToken;
+
+        protected ExpressionParserBase(string expression)
+        {
+            _text = expression ?? throw new ArgumentNullException(nameof(expression));
+            TextLen = _text.Length;
+
+            SetTextPos(0);
+        }
+
+        private void NextChar()
+        {
+            if (TextPos < TextLen) TextPos++;
+            _ch = TextPos < TextLen ? _text[TextPos] : '\0';
+        }
+
+        protected void SetTextPos(int pos)
+        {
+            TextPos = pos;
+            _ch = TextPos < TextLen ? _text[TextPos] : '\0';
+        }
+
+        private void ValidateDigit()
+        {
+            if (!char.IsDigit(_ch)) throw ParseError(TextPos, Res.DigitExpected);
+        }
+
+        protected Exception ParseError(string format, params object[] args)
+        {
+            return ParseError(CurrentToken.Pos, format, args);
+        }
+
+        protected Exception ParseError(int pos, string format, params object[] args)
+        {
+            return new ParseException(string.Format(CultureInfo.CurrentCulture, format, args), pos);
+        }
+
+        protected void NextToken()
+        {
+            while (char.IsWhiteSpace(_ch)) NextChar();
+            var tokenPos = TextPos;
+
+            if (false == ParseToken(out var t))
+            {
+                throw ParseError(TextPos, Res.InvalidCharacter, _ch);
+            }
+
+            CurrentToken.Id = t;
+            CurrentToken.Text = _text.Substring(tokenPos, TextPos - tokenPos);
+            CurrentToken.Pos = tokenPos;
+        }
+
+        private bool ParseToken(out TokenId t)
+        {
+            switch (_ch)
+            {
+                case '!':
+                    NextChar();
+                    if (_ch == '=')
+                    {
+                        NextChar();
+                        t = TokenId.ExclamationEqual;
+                    }
+                    else
+                    {
+                        t = TokenId.Exclamation;
+                    }
+
+                    break;
+
+                case '%':
+                    NextChar();
+                    t = TokenId.Percent;
+                    break;
+
+                case '&':
+                    NextChar();
+                    if (_ch == '&')
+                    {
+                        NextChar();
+                        t = TokenId.DoubleAmphersand;
+                    }
+                    else
+                    {
+                        t = TokenId.Amphersand;
+                    }
+
+                    break;
+
+                case '(':
+                    NextChar();
+                    t = TokenId.OpenParen;
+                    break;
+
+                case ')':
+                    NextChar();
+                    t = TokenId.CloseParen;
+                    break;
+
+                case '*':
+                    NextChar();
+                    t = TokenId.Asterisk;
+                    break;
+
+                case '+':
+                    NextChar();
+                    t = TokenId.Plus;
+                    break;
+
+                case ',':
+                    NextChar();
+                    t = TokenId.Comma;
+                    break;
+
+                case '-':
+                    NextChar();
+                    t = TokenId.Minus;
+                    break;
+
+                case '.':
+                    NextChar();
+                    t = TokenId.Dot;
+                    break;
+
+                case '/':
+                    NextChar();
+                    t = TokenId.Slash;
+                    break;
+
+                case ':':
+                    NextChar();
+                    t = TokenId.Colon;
+                    break;
+
+                case '<':
+                    NextChar();
+                    if (_ch == '=')
+                    {
+                        NextChar();
+                        t = TokenId.LessThanEqual;
+                    }
+                    else if (_ch == '>')
+                    {
+                        NextChar();
+                        t = TokenId.LessGreater;
+                    }
+                    else
+                    {
+                        t = TokenId.LessThan;
+                    }
+
+                    break;
+
+                case '=':
+                    NextChar();
+                    if (_ch == '=')
+                    {
+                        NextChar();
+                        t = TokenId.DoubleEqual;
+                    }
+                    else
+                    {
+                        t = TokenId.Equal;
+                    }
+
+                    break;
+
+                case '>':
+                    NextChar();
+                    if (_ch == '=')
+                    {
+                        NextChar();
+                        t = TokenId.GreaterThanEqual;
+                    }
+                    else
+                    {
+                        t = TokenId.GreaterThan;
+                    }
+
+                    break;
+
+                case '?':
+                    NextChar();
+                    t = TokenId.Question;
+                    break;
+
+                case '[':
+                    NextChar();
+                    t = TokenId.OpenBracket;
+                    break;
+
+                case ']':
+                    NextChar();
+                    t = TokenId.CloseBracket;
+                    break;
+
+                case '|':
+                    NextChar();
+                    if (_ch == '|')
+                    {
+                        NextChar();
+                        t = TokenId.DoubleBar;
+                    }
+                    else
+                    {
+                        t = TokenId.Bar;
+                    }
+
+                    break;
+
+                case '"':
+                case '\'':
+                    var quote = _ch;
+                    do
+                    {
+                        NextChar();
+                        while (TextPos < TextLen && _ch != quote) NextChar();
+                        if (TextPos == TextLen)
+                            throw ParseError(TextPos, Res.UnterminatedStringLiteral);
+                        NextChar();
+                    } while (_ch == quote);
+
+                    t = TokenId.StringLiteral;
+                    break;
+
+                default:
+                    if (char.IsLetter(_ch) || _ch == '@' || _ch == '_')
+                    {
+                        do
+                        {
+                            NextChar();
+                        } while (char.IsLetterOrDigit(_ch) || _ch == '_');
+
+                        t = TokenId.Identifier;
+                        break;
+                    }
+
+                    if (char.IsDigit(_ch))
+                    {
+                        t = TokenId.IntegerLiteral;
+                        do
+                        {
+                            NextChar();
+                        } while (char.IsDigit(_ch));
+
+                        if (_ch == '.')
+                        {
+                            t = TokenId.RealLiteral;
+                            NextChar();
+                            ValidateDigit();
+                            do
+                            {
+                                NextChar();
+                            } while (char.IsDigit(_ch));
+                        }
+
+                        if (_ch == 'E' || _ch == 'e')
+                        {
+                            t = TokenId.RealLiteral;
+                            NextChar();
+                            if (_ch == '+' || _ch == '-') NextChar();
+                            ValidateDigit();
+                            do
+                            {
+                                NextChar();
+                            } while (char.IsDigit(_ch));
+                        }
+
+                        if (_ch == 'F' || _ch == 'f') NextChar();
+                        break;
+                    }
+
+                    if (TextPos == TextLen)
+                    {
+                        t = TokenId.End;
+                        break;
+                    }
+
+                    t = TokenId.Unknown;
+                    return false;
+            }
+
+            return true;
+        }
+    }
+}

+ 21 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/ParseException.cs

@@ -0,0 +1,21 @@
+using System;
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal sealed class ParseException : Exception
+    {
+        public ParseException(string message, int position)
+            : base(message)
+        {
+            Position = position;
+        }
+
+        public int Position { get; }
+
+        //TODO: 看看有没有影响 然后移除
+        //public override string ToString()
+        //{
+        //    return string.Format(Res.ParseExceptionFormat, Message, Position);
+        //}
+    }
+}

+ 53 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/Res.cs

@@ -0,0 +1,53 @@
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal static class Res
+    {
+        // ReSharper disable UnusedMember.Global
+        public const string DuplicateIdentifier = "The identifier '{0}' was defined more than once";
+
+        public const string ExpressionTypeMismatch = "Expression of type '{0}' expected";
+        public const string ExpressionExpected = "Expression expected";
+        public const string InvalidCharacterLiteral = "Character literal must contain exactly one character";
+        public const string InvalidIntegerLiteral = "Invalid integer literal '{0}'";
+        public const string InvalidRealLiteral = "Invalid real literal '{0}'";
+        public const string UnknownIdentifier = "Unknown identifier '{0}'";
+        public const string NoItInScope = "No 'it' is in scope";
+        public const string IifRequiresThreeArgs = "The 'iif' function requires three arguments";
+        public const string FirstExprMustBeBool = "The first expression must be of type 'Boolean'";
+        public const string BothTypesConvertToOther = "Both of the types '{0}' and '{1}' convert to the other";
+        public const string NeitherTypeConvertsToOther = "Neither of the types '{0}' and '{1}' converts to the other";
+        public const string MissingAsClause = "Expression is missing an 'as' clause";
+        public const string ArgsIncompatibleWithLambda = "Argument list incompatible with lambda expression";
+        public const string TypeHasNoNullableForm = "Type '{0}' has no nullable form";
+        public const string NoMatchingConstructor = "No matching constructor in type '{0}'";
+        public const string AmbiguousConstructorInvocation = "Ambiguous invocation of '{0}' constructor";
+        public const string CannotConvertValue = "A value of type '{0}' cannot be converted to type '{1}'";
+        public const string NoApplicableMethod = "No applicable method '{0}' exists in type '{1}'";
+        public const string MethodsAreInaccessible = "Methods on type '{0}' are not accessible";
+        public const string MethodIsVoid = "Method '{0}' in type '{1}' does not return a value";
+        public const string AmbiguousMethodInvocation = "Ambiguous invocation of method '{0}' in type '{1}'";
+        public const string UnknownPropertyOrField = "No property or field '{0}' exists in type '{1}'";
+        public const string NoApplicableAggregate = "No applicable aggregate method '{0}' exists";
+        public const string CannotIndexMultiDimArray = "Indexing of multi-dimensional arrays is not supported";
+        public const string InvalidIndex = "Array index must be an integer expression";
+        public const string NoApplicableIndexer = "No applicable indexer exists in type '{0}'";
+        public const string AmbiguousIndexerInvocation = "Ambiguous invocation of indexer in type '{0}'";
+        public const string IncompatibleOperand = "Operator '{0}' incompatible with operand type '{1}'";
+        public const string IncompatibleOperands = "Operator '{0}' incompatible with operand types '{1}' and '{2}'";
+        public const string UnterminatedStringLiteral = "Unterminated string literal";
+        public const string InvalidCharacter = "Syntax error '{0}'";
+        public const string DigitExpected = "Digit expected";
+        public const string SyntaxError = "Syntax error";
+        public const string TokenExpected = "{0} expected";
+        public const string ParseExceptionFormat = "{0} (at index {1})";
+        public const string ColonExpected = "':' expected";
+        public const string OpenParenExpected = "'(' expected";
+        public const string CloseParenOrOperatorExpected = "')' or operator expected";
+        public const string CloseParenOrCommaExpected = "')' or ',' expected";
+        public const string DotOrOpenParenExpected = "'.' or '(' expected";
+        public const string OpenBracketExpected = "'[' expected";
+        public const string CloseBracketOrCommaExpected = "']' or ',' expected";
+        public const string IdentifierExpected = "Identifier expected";
+        // ReSharper restore UnusedMember.Global
+    }
+}

+ 4 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/SOURCE.txt

@@ -0,0 +1,4 @@
+动态 Linq 表达式解析器
+将文本表达式解析为可编译执行的表达式树
+来自 github.com/kahanu/System.Linq.Dynamic/blob/94c9055abe0b1ad008655f65b76e9e25c1536b62/Src/System.Linq.Dynamic/DynamicLinq.cs
+有清扫和变形

+ 44 - 0
FormulaEnginePoC/FormulaEngine/Internal/DynamicLinq/Signature.cs

@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq
+{
+    internal class Signature : IEquatable<Signature>
+    {
+        public readonly DynamicProperty[] Properties;
+        public readonly int HashCode;
+
+        public Signature(IEnumerable<DynamicProperty> properties)
+        {
+            Properties = properties.ToArray();
+            HashCode = 0;
+            foreach (var p in Properties)
+            {
+                HashCode ^= p.Name.GetHashCode() ^ p.Type.GetHashCode();
+            }
+        }
+
+        public override int GetHashCode()
+        {
+            return HashCode;
+        }
+
+        public override bool Equals(object obj)
+        {
+            return obj is Signature o && Equals(o);
+        }
+
+        public bool Equals(Signature other)
+        {
+            if (other == null) throw new ArgumentNullException(nameof(other));
+            if (Properties.Length != other.Properties.Length) return false;
+            for (var i = 0; i < Properties.Length; i++)
+            {
+                if (Properties[i].Name != other.Properties[i].Name ||
+                    Properties[i].Type != other.Properties[i].Type) return false;
+            }
+            return true;
+        }
+    }
+}

+ 1 - 0
FormulaEnginePoC/FormulaEngine/Internal/FormulaCompiler.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Linq.Expressions;
+using DynamicExpression = FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq.DynamicExpression;
 
 namespace FormulaEnginePoC.FormulaEngine.Internal
 {

+ 60 - 39
FormulaEnginePoC/FormulaEngine/Internal/FormulaConverter.cs

@@ -1,13 +1,33 @@
-using System;
+using FormulaEnginePoC.FormulaEngine.Internal.DynamicLinq;
+using FormulaEnginePoC.FormulaEngine.Internal.Mapping;
 using System.Collections.Generic;
 using System.Linq;
 using System.Text;
-using FormulaEnginePoC.FormulaEngine.Internal.Mapping;
 
 namespace FormulaEnginePoC.FormulaEngine.Internal
 {
     internal static class FormulaConverter
     {
+        private class ExpressTokenParser : ExpressionParserBase
+        {
+            private readonly Token[] _tokens;
+
+            public Token[] Tokens => _tokens.ToArray();
+
+            public ExpressTokenParser(string expression) : base(expression)
+            {
+                var lst = new List<Token>();
+
+                while (TextPos < TextLen)
+                {
+                    NextToken();
+                    lst.Add(CurrentToken);
+                }
+
+                _tokens = lst.ToArray();
+            }
+        }
+
         private static readonly Dictionary<string, string> Operators;
 
         private static readonly Dictionary<string, string> Functions;
@@ -46,54 +66,55 @@ namespace FormulaEnginePoC.FormulaEngine.Internal
             };
         }
 
-        private static string FullWidthToHalfWith(string fullWidth)
-        {
-            var c = fullWidth.ToCharArray();
-            for (var i = 0; i < c.Length; i++)
-            {
-                if (c[i] > 65280 && c[i] < 65375)
-                    c[i] = (char)(c[i] - 65248);
-            }
-            return new string(c);
-        }
-
         public static string Convert(string friendInput, TypeMapping mapping)
         {
-            var spaceProcessed = friendInput.Replace((char)12288, (char)32);
-
-            var tokens = spaceProcessed.Split(new[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries)
-                .SelectMany(p => p.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries))
-                .ToArray();
-
             var code = new StringBuilder();
 
-            foreach (var token in tokens)
+            var parser = new ExpressTokenParser(friendInput);
+            foreach (var token in parser.Tokens)
             {
-                var converted = ConvertInternal(mapping, token)
-                             ?? ConvertInternal(mapping, FullWidthToHalfWith(token));
-
-                if (null == converted) throw new ArgumentException("无法识别:" + token);
-
-                code.Append($" {converted} ");
+                code.Append(" ");
+
+                if (token.Id == ExpressionParserBase.TokenId.Identifier)
+                {
+                    if (Operators.TryGetValue(token.Text, out var opExp))
+                    {
+                        code.Append(opExp);
+                    }
+                    else if (Functions.TryGetValue(token.Text, out var funcExp))
+                    {
+                        code.Append(funcExp);
+                    }
+                    else if (mapping.TryGetPropertyName(token.Text, out var propertyName))
+                    {
+                        code.Append($"p.{propertyName}");
+                    }
+                    else
+                    {
+                        code.Append(token.Text);
+                    }
+                }
+                else
+                {
+                    code.Append(token.Text);
+                }
             }
 
             return code.ToString();
         }
+    }
 
-        private static string ConvertInternal(TypeMapping mapping, string token)
+    internal static class StringUtils
+    {
+        public static string FullWidthToHalfWith(string fullWidth)
         {
-            if (Operators.TryGetValue(token, out var opExp)) return opExp;
-            if (Functions.TryGetValue(token, out var funcExp)) return funcExp;
-            if (mapping.TryGetPropertyName(token, out var propertyName)) return $"p.{propertyName}";
-
-            if (token.StartsWith("@")) return token;
-
-            if (decimal.TryParse(token, out _)) return token;
-
-
-            return null;
+            var c = fullWidth.ToCharArray();
+            for (var i = 0; i < c.Length; i++)
+            {
+                if (c[i] > 65280 && c[i] < 65375)
+                    c[i] = (char)(c[i] - 65248);
+            }
+            return new string(c);
         }
-
-
     }
 }

+ 15 - 1
FormulaEnginePoC/FormulaEnginePoC.csproj

@@ -50,6 +50,16 @@
     <Compile Include="FormulaEngine\CalcFormula.cs" />
     <Compile Include="FormulaEngine\FilterFormula.cs" />
     <Compile Include="FormulaEngine\Formula.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\ClassFactory.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\DynamicClass.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\DynamicExpression.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\DynamicOrdering.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\DynamicProperty.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\ExpressionParser.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\ExpressionParserBase.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\ParseException.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\Res.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\Signature.cs" />
     <Compile Include="FormulaEngine\Internal\FormulaCompiler.cs" />
     <Compile Include="FormulaEngine\Internal\FormulaConverter.cs" />
     <Compile Include="FormulaEngine\Internal\Mapping\PropertyMapping.cs" />
@@ -61,7 +71,7 @@
     <Compile Include="SampleForm.Designer.cs">
       <DependentUpon>SampleForm.cs</DependentUpon>
     </Compile>
-    <Compile Include="FormulaEngine\Internal\DynamicLinq.cs" />
+    <Compile Include="FormulaEngine\Internal\DynamicLinq\DynamicQueryable.cs" />
     <Compile Include="Program.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="SampleClasses\Book.cs" />
@@ -92,5 +102,9 @@
   <ItemGroup>
     <None Include="App.config" />
   </ItemGroup>
+  <ItemGroup />
+  <ItemGroup>
+    <Content Include="FormulaEngine\Internal\DynamicLinq\SOURCE.txt" />
+  </ItemGroup>
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
 </Project>

+ 1 - 1
FormulaEnginePoC/SampleForm.cs

@@ -72,7 +72,7 @@ namespace FormulaEnginePoC
             }
             catch (Exception exception)
             {
-                CodeTextBox.Text = exception.Message;
+                CodeTextBox.Text = exception.ToString();
             }
         }