Mercurial > silverbladetech
diff MetroWpf/MetroWpf.Framework/Extensions/Dynamic.cs @ 15:060f02cd4591
Initial commit, pre airport work
author | stevenh7776 stevenhollidge@hotmail.com |
---|---|
date | Mon, 12 Mar 2012 23:05:21 +0800 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MetroWpf/MetroWpf.Framework/Extensions/Dynamic.cs Mon Mar 12 23:05:21 2012 +0800 @@ -0,0 +1,2288 @@ +//Copyright (C) Microsoft Corporation. All rights reserved. + +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Reflection.Emit; +using System.Text; +using System.Threading; + +namespace System.Linq.Dynamic +{ + public static class DynamicQueryable + { + 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("source"); + if (predicate == null) throw new ArgumentNullException("predicate"); + LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, typeof(bool), predicate, values); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Where", + new Type[] { 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("source"); + if (selector == null) throw new ArgumentNullException("selector"); + LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Select", + new Type[] { 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("source"); + if (ordering == null) throw new ArgumentNullException("ordering"); + ParameterExpression[] parameters = new ParameterExpression[] { + Expression.Parameter(source.ElementType, "") }; + ExpressionParser parser = new ExpressionParser(parameters, ordering, values); + IEnumerable<DynamicOrdering> orderings = parser.ParseOrdering(); + Expression queryExpr = source.Expression; + string methodAsc = "OrderBy"; + string methodDesc = "OrderByDescending"; + foreach (DynamicOrdering o in orderings) + { + queryExpr = Expression.Call( + typeof(Queryable), o.Ascending ? methodAsc : methodDesc, + new Type[] { 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("source"); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Take", + new Type[] { source.ElementType }, + source.Expression, Expression.Constant(count))); + } + + public static IQueryable Skip(this IQueryable source, int count) + { + if (source == null) throw new ArgumentNullException("source"); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "Skip", + new Type[] { 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("source"); + if (keySelector == null) throw new ArgumentNullException("keySelector"); + if (elementSelector == null) throw new ArgumentNullException("elementSelector"); + LambdaExpression keyLambda = DynamicExpression.ParseLambda(source.ElementType, null, keySelector, values); + LambdaExpression elementLambda = DynamicExpression.ParseLambda(source.ElementType, null, elementSelector, values); + return source.Provider.CreateQuery( + Expression.Call( + typeof(Queryable), "GroupBy", + new Type[] { 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("source"); + return (bool)source.Provider.Execute( + Expression.Call( + typeof(Queryable), "Any", + new Type[] { source.ElementType }, source.Expression)); + } + + public static int Count(this IQueryable source) + { + if (source == null) throw new ArgumentNullException("source"); + return (int)source.Provider.Execute( + Expression.Call( + typeof(Queryable), "Count", + new Type[] { source.ElementType }, source.Expression)); + } + } + + public abstract class DynamicClass + { + public override string ToString() + { + PropertyInfo[] props = this.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public); + StringBuilder sb = new StringBuilder(); + sb.Append("{"); + for (int 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(); + } + } + + public class DynamicProperty + { + string name; + Type type; + + public DynamicProperty(string name, Type type) + { + if (name == null) throw new ArgumentNullException("name"); + if (type == null) throw new ArgumentNullException("type"); + this.name = name; + this.type = type; + } + + public string Name + { + get { return name; } + } + + public Type Type + { + get { return type; } + } + } + + public static class DynamicExpression + { + public static Expression Parse(Type resultType, string expression, params object[] values) + { + ExpressionParser 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 ParameterExpression[] { Expression.Parameter(itType, "") }, resultType, expression, values); + } + + public static LambdaExpression ParseLambda(ParameterExpression[] parameters, Type resultType, string expression, params object[] values) + { + ExpressionParser parser = new ExpressionParser(parameters, expression, values); + return Expression.Lambda(parser.Parse(resultType), parameters); + } + + public static Expression<Func<T, S>> ParseLambda<T, S>(string expression, params object[] values) + { + return (Expression<Func<T, S>>)ParseLambda(typeof(T), typeof(S), 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); + } + } + + internal class DynamicOrdering + { + public Expression Selector; + public bool Ascending; + } + + internal class Signature : IEquatable<Signature> + { + public DynamicProperty[] properties; + public int hashCode; + + public Signature(IEnumerable<DynamicProperty> properties) + { + this.properties = properties.ToArray(); + hashCode = 0; + foreach (DynamicProperty 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 ? Equals((Signature)obj) : false; + } + + public bool Equals(Signature other) + { + if (properties.Length != other.properties.Length) return false; + for (int 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; + } + } + + internal class ClassFactory + { + public static readonly ClassFactory Instance = new ClassFactory(); + + static ClassFactory() { } // Trigger lazy initialization of static fields + + ModuleBuilder module; + Dictionary<Signature, Type> classes; + int classCount; + ReaderWriterLock rwLock; + + private ClassFactory() + { + AssemblyName name = new AssemblyName("DynamicClasses"); + AssemblyBuilder assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(name, AssemblyBuilderAccess.Run); +#if ENABLE_LINQ_PARTIAL_TRUST + new ReflectionPermission(PermissionState.Unrestricted).Assert(); +#endif + try + { + module = assembly.DefineDynamicModule("Module"); + } + 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 + { + Signature signature = new Signature(properties); + Type type; + if (!classes.TryGetValue(signature, out type)) + { + type = CreateDynamicClass(signature.properties); + classes.Add(signature, type); + } + return type; + } + finally + { + rwLock.ReleaseReaderLock(); + } + } + + Type CreateDynamicClass(DynamicProperty[] properties) + { + LockCookie cookie = rwLock.UpgradeToWriterLock(Timeout.Infinite); + try + { + string typeName = "DynamicClass" + (classCount + 1); +#if ENABLE_LINQ_PARTIAL_TRUST + new ReflectionPermission(PermissionState.Unrestricted).Assert(); +#endif + try + { + TypeBuilder tb = this.module.DefineType(typeName, TypeAttributes.Class | + TypeAttributes.Public, typeof(DynamicClass)); + FieldInfo[] fields = GenerateProperties(tb, properties); + GenerateEquals(tb, fields); + GenerateGetHashCode(tb, fields); + Type result = tb.CreateType(); + classCount++; + return result; + } + finally + { +#if ENABLE_LINQ_PARTIAL_TRUST + PermissionSet.RevertAssert(); +#endif + } + } + finally + { + rwLock.DowngradeFromWriterLock(ref cookie); + } + } + + FieldInfo[] GenerateProperties(TypeBuilder tb, DynamicProperty[] properties) + { + FieldInfo[] fields = new FieldBuilder[properties.Length]; + for (int i = 0; i < properties.Length; i++) + { + DynamicProperty dp = properties[i]; + FieldBuilder fb = tb.DefineField("_" + dp.Name, dp.Type, FieldAttributes.Private); + PropertyBuilder pb = tb.DefineProperty(dp.Name, PropertyAttributes.HasDefault, dp.Type, null); + MethodBuilder mbGet = tb.DefineMethod("get_" + dp.Name, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + dp.Type, Type.EmptyTypes); + ILGenerator genGet = mbGet.GetILGenerator(); + genGet.Emit(OpCodes.Ldarg_0); + genGet.Emit(OpCodes.Ldfld, fb); + genGet.Emit(OpCodes.Ret); + MethodBuilder mbSet = tb.DefineMethod("set_" + dp.Name, + MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig, + null, new Type[] { dp.Type }); + ILGenerator 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; + } + + void GenerateEquals(TypeBuilder tb, FieldInfo[] fields) + { + MethodBuilder mb = tb.DefineMethod("Equals", + MethodAttributes.Public | MethodAttributes.ReuseSlot | + MethodAttributes.Virtual | MethodAttributes.HideBySig, + typeof(bool), new Type[] { typeof(object) }); + ILGenerator gen = mb.GetILGenerator(); + LocalBuilder other = gen.DeclareLocal(tb); + Label 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 (FieldInfo field in fields) + { + Type ft = field.FieldType; + Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); + next = gen.DefineLabel(); + 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); + gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("Equals", new Type[] { 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); + } + + void GenerateGetHashCode(TypeBuilder tb, FieldInfo[] fields) + { + MethodBuilder mb = tb.DefineMethod("GetHashCode", + MethodAttributes.Public | MethodAttributes.ReuseSlot | + MethodAttributes.Virtual | MethodAttributes.HideBySig, + typeof(int), Type.EmptyTypes); + ILGenerator gen = mb.GetILGenerator(); + gen.Emit(OpCodes.Ldc_I4_0); + foreach (FieldInfo field in fields) + { + Type ft = field.FieldType; + Type ct = typeof(EqualityComparer<>).MakeGenericType(ft); + gen.EmitCall(OpCodes.Call, ct.GetMethod("get_Default"), null); + gen.Emit(OpCodes.Ldarg_0); + gen.Emit(OpCodes.Ldfld, field); + gen.EmitCall(OpCodes.Callvirt, ct.GetMethod("GetHashCode", new Type[] { ft }), null); + gen.Emit(OpCodes.Xor); + } + gen.Emit(OpCodes.Ret); + } + } + + public sealed class ParseException : Exception + { + int position; + + public ParseException(string message, int position) + : base(message) + { + this.position = position; + } + + public int Position + { + get { return position; } + } + + public override string ToString() + { + return string.Format(Res.ParseExceptionFormat, Message, position); + } + } + + internal class ExpressionParser + { + struct Token + { + public TokenId id; + public string text; + public int pos; + } + + enum TokenId + { + 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 + } + + interface ILogicalSignatures + { + void F(bool x, bool y); + void F(bool? x, bool? y); + } + + interface IArithmeticSignatures + { + void F(int x, int y); + void F(uint x, uint y); + void F(long x, long y); + void F(ulong x, ulong y); + void F(float x, float y); + void F(double x, double y); + void F(decimal x, decimal y); + void F(int? x, int? y); + void F(uint? x, uint? y); + void F(long? x, long? y); + void F(ulong? x, ulong? y); + void F(float? x, float? y); + void F(double? x, double? y); + void F(decimal? x, decimal? y); + } + + interface IRelationalSignatures : IArithmeticSignatures + { + void F(string x, string y); + void F(char x, char y); + void F(DateTime x, DateTime y); + void F(TimeSpan x, TimeSpan y); + void F(char? x, char? y); + void F(DateTime? x, DateTime? y); + void F(TimeSpan? x, TimeSpan? y); + } + + interface IEqualitySignatures : IRelationalSignatures + { + void F(bool x, bool y); + void F(bool? x, bool? y); + } + + interface IAddSignatures : IArithmeticSignatures + { + void F(DateTime x, TimeSpan y); + void F(TimeSpan x, TimeSpan y); + void F(DateTime? x, TimeSpan? y); + void F(TimeSpan? x, TimeSpan? y); + } + + interface ISubtractSignatures : IAddSignatures + { + void F(DateTime x, DateTime y); + void F(DateTime? x, DateTime? y); + } + + interface INegationSignatures + { + void F(int x); + void F(long x); + void F(float x); + void F(double x); + void F(decimal x); + void F(int? x); + void F(long? x); + void F(float? x); + void F(double? x); + void F(decimal? x); + } + + interface INotSignatures + { + void F(bool x); + void F(bool? x); + } + + interface IEnumerableSignatures + { + void Where(bool predicate); + void Any(); + void Any(bool predicate); + void All(bool predicate); + void Count(); + void Count(bool predicate); + void Min(object selector); + void Max(object selector); + void Sum(int selector); + void Sum(int? selector); + void Sum(long selector); + void Sum(long? selector); + void Sum(float selector); + void Sum(float? selector); + void Sum(double selector); + void Sum(double? selector); + void Sum(decimal selector); + void Sum(decimal? selector); + void Average(int selector); + void Average(int? selector); + void Average(long selector); + void Average(long? selector); + void Average(float selector); + void Average(float? selector); + void Average(double selector); + void Average(double? selector); + void Average(decimal selector); + void Average(decimal? selector); + } + + static readonly Type[] predefinedTypes = { + typeof(Object), + typeof(Boolean), + typeof(Char), + typeof(String), + typeof(SByte), + typeof(Byte), + typeof(Int16), + typeof(UInt16), + typeof(Int32), + typeof(UInt32), + typeof(Int64), + typeof(UInt64), + typeof(Single), + typeof(Double), + typeof(Decimal), + typeof(DateTime), + typeof(TimeSpan), + typeof(Guid), + typeof(Math), + typeof(Convert) + }; + + static readonly Expression trueLiteral = Expression.Constant(true); + static readonly Expression falseLiteral = Expression.Constant(false); + static readonly Expression nullLiteral = Expression.Constant(null); + + static readonly string keywordIt = "it"; + static readonly string keywordIif = "iif"; + static readonly string keywordNew = "new"; + + static Dictionary<string, object> keywords; + + Dictionary<string, object> symbols; + IDictionary<string, object> externals; + Dictionary<Expression, string> literals; + ParameterExpression it; + string text; + int textPos; + int textLen; + char ch; + Token token; + + public ExpressionParser(ParameterExpression[] parameters, string expression, object[] values) + { + if (expression == null) throw new ArgumentNullException("expression"); + if (keywords == null) keywords = CreateKeywords(); + symbols = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); + literals = new Dictionary<Expression, string>(); + if (parameters != null) ProcessParameters(parameters); + if (values != null) ProcessValues(values); + text = expression; + textLen = text.Length; + SetTextPos(0); + NextToken(); + } + + void ProcessParameters(ParameterExpression[] parameters) + { + foreach (ParameterExpression pe in parameters) + if (!String.IsNullOrEmpty(pe.Name)) + AddSymbol(pe.Name, pe); + if (parameters.Length == 1 && String.IsNullOrEmpty(parameters[0].Name)) + it = parameters[0]; + } + + void ProcessValues(object[] values) + { + for (int i = 0; i < values.Length; i++) + { + object value = values[i]; + if (i == values.Length - 1 && value is IDictionary<string, object>) + { + externals = (IDictionary<string, object>)value; + } + else + { + AddSymbol("@" + i.ToString(System.Globalization.CultureInfo.InvariantCulture), value); + } + } + } + + void AddSymbol(string name, object value) + { + if (symbols.ContainsKey(name)) + throw ParseError(Res.DuplicateIdentifier, name); + symbols.Add(name, value); + } + + public Expression Parse(Type resultType) + { + int exprPos = token.pos; + Expression expr = ParseExpression(); + if (resultType != null) + if ((expr = PromoteExpression(expr, resultType, true)) == null) + throw ParseError(exprPos, Res.ExpressionTypeMismatch, GetTypeName(resultType)); + ValidateToken(TokenId.End, Res.SyntaxError); + return expr; + } + +#pragma warning disable 0219 + public IEnumerable<DynamicOrdering> ParseOrdering() + { + List<DynamicOrdering> orderings = new List<DynamicOrdering>(); + while (true) + { + Expression expr = ParseExpression(); + bool ascending = true; + if (TokenIdentifierIs("asc") || TokenIdentifierIs("ascending")) + { + NextToken(); + } + else if (TokenIdentifierIs("desc") || TokenIdentifierIs("descending")) + { + NextToken(); + ascending = false; + } + orderings.Add(new DynamicOrdering { Selector = expr, Ascending = ascending }); + if (token.id != TokenId.Comma) break; + NextToken(); + } + ValidateToken(TokenId.End, Res.SyntaxError); + return orderings; + } +#pragma warning restore 0219 + + // ?: operator + Expression ParseExpression() + { + int errorPos = token.pos; + Expression expr = ParseLogicalOr(); + if (token.id == TokenId.Question) + { + NextToken(); + Expression expr1 = ParseExpression(); + ValidateToken(TokenId.Colon, Res.ColonExpected); + NextToken(); + Expression expr2 = ParseExpression(); + expr = GenerateConditional(expr, expr1, expr2, errorPos); + } + return expr; + } + + // ||, or operator + Expression ParseLogicalOr() + { + Expression left = ParseLogicalAnd(); + while (token.id == TokenId.DoubleBar || TokenIdentifierIs("or")) + { + Token op = token; + NextToken(); + Expression right = ParseLogicalAnd(); + CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos); + left = Expression.OrElse(left, right); + } + return left; + } + + // &&, and operator + Expression ParseLogicalAnd() + { + Expression left = ParseComparison(); + while (token.id == TokenId.DoubleAmphersand || TokenIdentifierIs("and")) + { + Token op = token; + NextToken(); + Expression right = ParseComparison(); + CheckAndPromoteOperands(typeof(ILogicalSignatures), op.text, ref left, ref right, op.pos); + left = Expression.AndAlso(left, right); + } + return left; + } + + // =, ==, !=, <>, >, >=, <, <= operators + Expression ParseComparison() + { + Expression left = ParseAdditive(); + while (token.id == TokenId.Equal || token.id == TokenId.DoubleEqual || + token.id == TokenId.ExclamationEqual || token.id == TokenId.LessGreater || + token.id == TokenId.GreaterThan || token.id == TokenId.GreaterThanEqual || + token.id == TokenId.LessThan || token.id == TokenId.LessThanEqual) + { + Token op = token; + NextToken(); + Expression right = ParseAdditive(); + bool isEquality = op.id == TokenId.Equal || op.id == TokenId.DoubleEqual || + op.id == TokenId.ExclamationEqual || op.id == TokenId.LessGreater; + if (isEquality && !left.Type.IsValueType && !right.Type.IsValueType) + { + if (left.Type != right.Type) + { + if (left.Type.IsAssignableFrom(right.Type)) + { + right = Expression.Convert(right, left.Type); + } + else if (right.Type.IsAssignableFrom(left.Type)) + { + left = Expression.Convert(left, right.Type); + } + else + { + throw IncompatibleOperandsError(op.text, left, right, op.pos); + } + } + } + else if (IsEnumType(left.Type) || IsEnumType(right.Type)) + { + if (left.Type != right.Type) + { + Expression e; + if ((e = PromoteExpression(right, left.Type, true)) != null) + { + right = e; + } + else if ((e = PromoteExpression(left, right.Type, true)) != null) + { + left = e; + } + else + { + throw IncompatibleOperandsError(op.text, left, right, op.pos); + } + } + } + else + { + CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), + op.text, ref left, ref right, op.pos); + } + switch (op.id) + { + case TokenId.Equal: + case TokenId.DoubleEqual: + left = GenerateEqual(left, right); + break; + case TokenId.ExclamationEqual: + case TokenId.LessGreater: + left = GenerateNotEqual(left, right); + break; + case TokenId.GreaterThan: + left = GenerateGreaterThan(left, right); + break; + case TokenId.GreaterThanEqual: + left = GenerateGreaterThanEqual(left, right); + break; + case TokenId.LessThan: + left = GenerateLessThan(left, right); + break; + case TokenId.LessThanEqual: + left = GenerateLessThanEqual(left, right); + break; + } + } + return left; + } + + // +, -, & operators + Expression ParseAdditive() + { + Expression left = ParseMultiplicative(); + while (token.id == TokenId.Plus || token.id == TokenId.Minus || + token.id == TokenId.Amphersand) + { + Token op = token; + NextToken(); + Expression right = ParseMultiplicative(); + switch (op.id) + { + case TokenId.Plus: + if (left.Type == typeof(string) || right.Type == typeof(string)) + goto case TokenId.Amphersand; + CheckAndPromoteOperands(typeof(IAddSignatures), op.text, ref left, ref right, op.pos); + left = GenerateAdd(left, right); + break; + case TokenId.Minus: + CheckAndPromoteOperands(typeof(ISubtractSignatures), op.text, ref left, ref right, op.pos); + left = GenerateSubtract(left, right); + break; + case TokenId.Amphersand: + left = GenerateStringConcat(left, right); + break; + } + } + return left; + } + + // *, /, %, mod operators + Expression ParseMultiplicative() + { + Expression left = ParseUnary(); + while (token.id == TokenId.Asterisk || token.id == TokenId.Slash || + token.id == TokenId.Percent || TokenIdentifierIs("mod")) + { + Token op = token; + NextToken(); + Expression right = ParseUnary(); + CheckAndPromoteOperands(typeof(IArithmeticSignatures), op.text, ref left, ref right, op.pos); + switch (op.id) + { + case TokenId.Asterisk: + left = Expression.Multiply(left, right); + break; + case TokenId.Slash: + left = Expression.Divide(left, right); + break; + case TokenId.Percent: + case TokenId.Identifier: + left = Expression.Modulo(left, right); + break; + } + } + return left; + } + + // -, !, not unary operators + Expression ParseUnary() + { + if (token.id == TokenId.Minus || token.id == TokenId.Exclamation || + TokenIdentifierIs("not")) + { + Token op = token; + NextToken(); + if (op.id == TokenId.Minus && (token.id == TokenId.IntegerLiteral || + token.id == TokenId.RealLiteral)) + { + token.text = "-" + token.text; + token.pos = op.pos; + return ParsePrimary(); + } + Expression expr = ParseUnary(); + if (op.id == TokenId.Minus) + { + CheckAndPromoteOperand(typeof(INegationSignatures), op.text, ref expr, op.pos); + expr = Expression.Negate(expr); + } + else + { + CheckAndPromoteOperand(typeof(INotSignatures), op.text, ref expr, op.pos); + expr = Expression.Not(expr); + } + return expr; + } + return ParsePrimary(); + } + + Expression ParsePrimary() + { + Expression expr = ParsePrimaryStart(); + while (true) + { + if (token.id == TokenId.Dot) + { + NextToken(); + expr = ParseMemberAccess(null, expr); + } + else if (token.id == TokenId.OpenBracket) + { + expr = ParseElementAccess(expr); + } + else + { + break; + } + } + return expr; + } + + Expression ParsePrimaryStart() + { + switch (token.id) + { + case TokenId.Identifier: + return ParseIdentifier(); + case TokenId.StringLiteral: + return ParseStringLiteral(); + case TokenId.IntegerLiteral: + return ParseIntegerLiteral(); + case TokenId.RealLiteral: + return ParseRealLiteral(); + case TokenId.OpenParen: + return ParseParenExpression(); + default: + throw ParseError(Res.ExpressionExpected); + } + } + + Expression ParseStringLiteral() + { + ValidateToken(TokenId.StringLiteral); + char quote = token.text[0]; + string s = token.text.Substring(1, token.text.Length - 2); + int start = 0; + while (true) + { + int i = s.IndexOf(quote, start); + if (i < 0) break; + s = s.Remove(i, 1); + start = i + 1; + } + if (quote == '\'') + { + if (s.Length != 1) + throw ParseError(Res.InvalidCharacterLiteral); + NextToken(); + return CreateLiteral(s[0], s); + } + NextToken(); + return CreateLiteral(s, s); + } + + Expression ParseIntegerLiteral() + { + ValidateToken(TokenId.IntegerLiteral); + string text = token.text; + if (text[0] != '-') + { + ulong value; + if (!UInt64.TryParse(text, out value)) + throw ParseError(Res.InvalidIntegerLiteral, text); + NextToken(); + if (value <= (ulong)Int32.MaxValue) return CreateLiteral((int)value, text); + if (value <= (ulong)UInt32.MaxValue) return CreateLiteral((uint)value, text); + if (value <= (ulong)Int64.MaxValue) return CreateLiteral((long)value, text); + return CreateLiteral(value, text); + } + else + { + long value; + if (!Int64.TryParse(text, out value)) + throw ParseError(Res.InvalidIntegerLiteral, text); + NextToken(); + if (value >= Int32.MinValue && value <= Int32.MaxValue) + return CreateLiteral((int)value, text); + return CreateLiteral(value, text); + } + } + + Expression ParseRealLiteral() + { + ValidateToken(TokenId.RealLiteral); + string text = token.text; + object value = null; + char last = text[text.Length - 1]; + if (last == 'F' || last == 'f') + { + float f; + if (Single.TryParse(text.Substring(0, text.Length - 1), out f)) value = f; + } + else + { + double d; + if (Double.TryParse(text, out d)) value = d; + } + if (value == null) throw ParseError(Res.InvalidRealLiteral, text); + NextToken(); + return CreateLiteral(value, text); + } + + Expression CreateLiteral(object value, string text) + { + ConstantExpression expr = Expression.Constant(value); + literals.Add(expr, text); + return expr; + } + + Expression ParseParenExpression() + { + ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + NextToken(); + Expression e = ParseExpression(); + ValidateToken(TokenId.CloseParen, Res.CloseParenOrOperatorExpected); + NextToken(); + return e; + } + + Expression ParseIdentifier() + { + ValidateToken(TokenId.Identifier); + object value; + if (keywords.TryGetValue(token.text, out value)) + { + if (value is Type) return ParseTypeAccess((Type)value); + if (value == (object)keywordIt) return ParseIt(); + if (value == (object)keywordIif) return ParseIif(); + if (value == (object)keywordNew) return ParseNew(); + NextToken(); + return (Expression)value; + } + if (symbols.TryGetValue(token.text, out value) || + externals != null && externals.TryGetValue(token.text, out value)) + { + Expression expr = value as Expression; + if (expr == null) + { + expr = Expression.Constant(value); + } + else + { + LambdaExpression lambda = expr as LambdaExpression; + if (lambda != null) return ParseLambdaInvocation(lambda); + } + NextToken(); + return expr; + } + if (it != null) return ParseMemberAccess(null, it); + throw ParseError(Res.UnknownIdentifier, token.text); + } + + Expression ParseIt() + { + if (it == null) + throw ParseError(Res.NoItInScope); + NextToken(); + return it; + } + + Expression ParseIif() + { + int errorPos = token.pos; + NextToken(); + Expression[] args = ParseArgumentList(); + if (args.Length != 3) + throw ParseError(errorPos, Res.IifRequiresThreeArgs); + return GenerateConditional(args[0], args[1], args[2], errorPos); + } + + Expression GenerateConditional(Expression test, Expression expr1, Expression expr2, int errorPos) + { + if (test.Type != typeof(bool)) + throw ParseError(errorPos, Res.FirstExprMustBeBool); + if (expr1.Type != expr2.Type) + { + Expression expr1as2 = expr2 != nullLiteral ? PromoteExpression(expr1, expr2.Type, true) : null; + Expression expr2as1 = expr1 != nullLiteral ? PromoteExpression(expr2, expr1.Type, true) : null; + if (expr1as2 != null && expr2as1 == null) + { + expr1 = expr1as2; + } + else if (expr2as1 != null && expr1as2 == null) + { + expr2 = expr2as1; + } + else + { + string type1 = expr1 != nullLiteral ? expr1.Type.Name : "null"; + string type2 = expr2 != nullLiteral ? expr2.Type.Name : "null"; + if (expr1as2 != null && expr2as1 != null) + throw ParseError(errorPos, Res.BothTypesConvertToOther, type1, type2); + throw ParseError(errorPos, Res.NeitherTypeConvertsToOther, type1, type2); + } + } + return Expression.Condition(test, expr1, expr2); + } + + Expression ParseNew() + { + NextToken(); + ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + NextToken(); + List<DynamicProperty> properties = new List<DynamicProperty>(); + List<Expression> expressions = new List<Expression>(); + while (true) + { + int exprPos = token.pos; + Expression expr = ParseExpression(); + string propName; + if (TokenIdentifierIs("as")) + { + NextToken(); + propName = GetIdentifier(); + NextToken(); + } + else + { + MemberExpression me = expr as MemberExpression; + if (me == null) throw ParseError(exprPos, Res.MissingAsClause); + propName = me.Member.Name; + } + expressions.Add(expr); + properties.Add(new DynamicProperty(propName, expr.Type)); + if (token.id != TokenId.Comma) break; + NextToken(); + } + ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); + NextToken(); + Type type = DynamicExpression.CreateClass(properties); + MemberBinding[] bindings = new MemberBinding[properties.Count]; + for (int i = 0; i < bindings.Length; i++) + bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]); + return Expression.MemberInit(Expression.New(type), bindings); + } + + Expression ParseLambdaInvocation(LambdaExpression lambda) + { + int errorPos = token.pos; + NextToken(); + Expression[] args = ParseArgumentList(); + MethodBase method; + if (FindMethod(lambda.Type, "Invoke", false, args, out method) != 1) + throw ParseError(errorPos, Res.ArgsIncompatibleWithLambda); + return Expression.Invoke(lambda, args); + } + + Expression ParseTypeAccess(Type type) + { + int errorPos = token.pos; + NextToken(); + if (token.id == TokenId.Question) + { + if (!type.IsValueType || IsNullableType(type)) + throw ParseError(errorPos, Res.TypeHasNoNullableForm, GetTypeName(type)); + type = typeof(Nullable<>).MakeGenericType(type); + NextToken(); + } + if (token.id == TokenId.OpenParen) + { + Expression[] args = ParseArgumentList(); + MethodBase method; + switch (FindBestMethod(type.GetConstructors(), args, out method)) + { + case 0: + if (args.Length == 1) + return GenerateConversion(args[0], type, errorPos); + throw ParseError(errorPos, Res.NoMatchingConstructor, GetTypeName(type)); + case 1: + return Expression.New((ConstructorInfo)method, args); + default: + throw ParseError(errorPos, Res.AmbiguousConstructorInvocation, GetTypeName(type)); + } + } + ValidateToken(TokenId.Dot, Res.DotOrOpenParenExpected); + NextToken(); + return ParseMemberAccess(type, null); + } + + Expression GenerateConversion(Expression expr, Type type, int errorPos) + { + Type exprType = expr.Type; + if (exprType == type) return expr; + if (exprType.IsValueType && type.IsValueType) + { + if ((IsNullableType(exprType) || IsNullableType(type)) && + GetNonNullableType(exprType) == GetNonNullableType(type)) + return Expression.Convert(expr, type); + if ((IsNumericType(exprType) || IsEnumType(exprType)) && + (IsNumericType(type)) || IsEnumType(type)) + return Expression.ConvertChecked(expr, type); + } + if (exprType.IsAssignableFrom(type) || type.IsAssignableFrom(exprType) || + exprType.IsInterface || type.IsInterface) + return Expression.Convert(expr, type); + throw ParseError(errorPos, Res.CannotConvertValue, + GetTypeName(exprType), GetTypeName(type)); + } + + Expression ParseMemberAccess(Type type, Expression instance) + { + if (instance != null) type = instance.Type; + int errorPos = token.pos; + string id = GetIdentifier(); + NextToken(); + if (token.id == TokenId.OpenParen) + { + if (instance != null && type != typeof(string)) + { + Type enumerableType = FindGenericType(typeof(IEnumerable<>), type); + if (enumerableType != null) + { + Type elementType = enumerableType.GetGenericArguments()[0]; + return ParseAggregate(instance, elementType, id, errorPos); + } + } + Expression[] args = ParseArgumentList(); + MethodBase mb; + switch (FindMethod(type, id, instance == null, args, out mb)) + { + case 0: + throw ParseError(errorPos, Res.NoApplicableMethod, + id, GetTypeName(type)); + case 1: + MethodInfo method = (MethodInfo)mb; + if (!IsPredefinedType(method.DeclaringType)) + throw ParseError(errorPos, Res.MethodsAreInaccessible, GetTypeName(method.DeclaringType)); + if (method.ReturnType == typeof(void)) + throw ParseError(errorPos, Res.MethodIsVoid, + id, GetTypeName(method.DeclaringType)); + return Expression.Call(instance, (MethodInfo)method, args); + default: + throw ParseError(errorPos, Res.AmbiguousMethodInvocation, + id, GetTypeName(type)); + } + } + else + { + MemberInfo member = FindPropertyOrField(type, id, instance == null); + if (member == null) + throw ParseError(errorPos, Res.UnknownPropertyOrField, + id, GetTypeName(type)); + return member is PropertyInfo ? + Expression.Property(instance, (PropertyInfo)member) : + Expression.Field(instance, (FieldInfo)member); + } + } + + static Type FindGenericType(Type generic, Type type) + { + while (type != null && type != typeof(object)) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == generic) return type; + if (generic.IsInterface) + { + foreach (Type intfType in type.GetInterfaces()) + { + Type found = FindGenericType(generic, intfType); + if (found != null) return found; + } + } + type = type.BaseType; + } + return null; + } + + Expression ParseAggregate(Expression instance, Type elementType, string methodName, int errorPos) + { + ParameterExpression outerIt = it; + ParameterExpression innerIt = Expression.Parameter(elementType, ""); + it = innerIt; + Expression[] args = ParseArgumentList(); + it = outerIt; + MethodBase signature; + if (FindMethod(typeof(IEnumerableSignatures), methodName, false, args, out signature) != 1) + throw ParseError(errorPos, Res.NoApplicableAggregate, methodName); + Type[] typeArgs; + if (signature.Name == "Min" || signature.Name == "Max") + { + typeArgs = new Type[] { elementType, args[0].Type }; + } + else + { + typeArgs = new Type[] { elementType }; + } + if (args.Length == 0) + { + args = new Expression[] { instance }; + } + else + { + args = new Expression[] { instance, Expression.Lambda(args[0], innerIt) }; + } + return Expression.Call(typeof(Enumerable), signature.Name, typeArgs, args); + } + + Expression[] ParseArgumentList() + { + ValidateToken(TokenId.OpenParen, Res.OpenParenExpected); + NextToken(); + Expression[] args = token.id != TokenId.CloseParen ? ParseArguments() : new Expression[0]; + ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected); + NextToken(); + return args; + } + + Expression[] ParseArguments() + { + List<Expression> argList = new List<Expression>(); + while (true) + { + argList.Add(ParseExpression()); + if (token.id != TokenId.Comma) break; + NextToken(); + } + return argList.ToArray(); + } + + Expression ParseElementAccess(Expression expr) + { + int errorPos = token.pos; + ValidateToken(TokenId.OpenBracket, Res.OpenParenExpected); + NextToken(); + Expression[] args = ParseArguments(); + ValidateToken(TokenId.CloseBracket, Res.CloseBracketOrCommaExpected); + NextToken(); + if (expr.Type.IsArray) + { + if (expr.Type.GetArrayRank() != 1 || args.Length != 1) + throw ParseError(errorPos, Res.CannotIndexMultiDimArray); + Expression index = PromoteExpression(args[0], typeof(int), true); + if (index == null) + throw ParseError(errorPos, Res.InvalidIndex); + return Expression.ArrayIndex(expr, index); + } + else + { + MethodBase mb; + switch (FindIndexer(expr.Type, args, out mb)) + { + case 0: + throw ParseError(errorPos, Res.NoApplicableIndexer, + GetTypeName(expr.Type)); + case 1: + return Expression.Call(expr, (MethodInfo)mb, args); + default: + throw ParseError(errorPos, Res.AmbiguousIndexerInvocation, + GetTypeName(expr.Type)); + } + } + } + + static bool IsPredefinedType(Type type) + { + foreach (Type t in predefinedTypes) if (t == type) return true; + return false; + } + + static bool IsNullableType(Type type) + { + return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); + } + + static Type GetNonNullableType(Type type) + { + return IsNullableType(type) ? type.GetGenericArguments()[0] : type; + } + + static string GetTypeName(Type type) + { + Type baseType = GetNonNullableType(type); + string s = baseType.Name; + if (type != baseType) s += '?'; + return s; + } + + static bool IsNumericType(Type type) + { + return GetNumericTypeKind(type) != 0; + } + + static bool IsSignedIntegralType(Type type) + { + return GetNumericTypeKind(type) == 2; + } + + static bool IsUnsignedIntegralType(Type type) + { + return GetNumericTypeKind(type) == 3; + } + + static int GetNumericTypeKind(Type type) + { + type = GetNonNullableType(type); + if (type.IsEnum) return 0; + switch (Type.GetTypeCode(type)) + { + case TypeCode.Char: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return 1; + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + return 2; + case TypeCode.Byte: + case TypeCode.UInt16: + case TypeCode.UInt32: + case TypeCode.UInt64: + return 3; + default: + return 0; + } + } + + static bool IsEnumType(Type type) + { + return GetNonNullableType(type).IsEnum; + } + + void CheckAndPromoteOperand(Type signatures, string opName, ref Expression expr, int errorPos) + { + Expression[] args = new Expression[] { expr }; + MethodBase method; + if (FindMethod(signatures, "F", false, args, out method) != 1) + throw ParseError(errorPos, Res.IncompatibleOperand, + opName, GetTypeName(args[0].Type)); + expr = args[0]; + } + + void CheckAndPromoteOperands(Type signatures, string opName, ref Expression left, ref Expression right, int errorPos) + { + Expression[] args = new Expression[] { left, right }; + MethodBase method; + if (FindMethod(signatures, "F", false, args, out method) != 1) + throw IncompatibleOperandsError(opName, left, right, errorPos); + left = args[0]; + right = args[1]; + } + + Exception IncompatibleOperandsError(string opName, Expression left, Expression right, int pos) + { + return ParseError(pos, Res.IncompatibleOperands, + opName, GetTypeName(left.Type), GetTypeName(right.Type)); + } + + MemberInfo FindPropertyOrField(Type type, string memberName, bool staticAccess) + { + BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | + (staticAccess ? BindingFlags.Static : BindingFlags.Instance); + foreach (Type t in SelfAndBaseTypes(type)) + { + MemberInfo[] members = t.FindMembers(MemberTypes.Property | MemberTypes.Field, + flags, Type.FilterNameIgnoreCase, memberName); + if (members.Length != 0) return members[0]; + } + return null; + } + + int FindMethod(Type type, string methodName, bool staticAccess, Expression[] args, out MethodBase method) + { + BindingFlags flags = BindingFlags.Public | BindingFlags.DeclaredOnly | + (staticAccess ? BindingFlags.Static : BindingFlags.Instance); + foreach (Type t in SelfAndBaseTypes(type)) + { + MemberInfo[] members = t.FindMembers(MemberTypes.Method, + flags, Type.FilterNameIgnoreCase, methodName); + int count = FindBestMethod(members.Cast<MethodBase>(), args, out method); + if (count != 0) return count; + } + method = null; + return 0; + } + + int FindIndexer(Type type, Expression[] args, out MethodBase method) + { + foreach (Type t in SelfAndBaseTypes(type)) + { + MemberInfo[] members = t.GetDefaultMembers(); + if (members.Length != 0) + { + IEnumerable<MethodBase> methods = members. + OfType<PropertyInfo>(). + Select(p => (MethodBase)p.GetGetMethod()). + Where(m => m != null); + int count = FindBestMethod(methods, args, out method); + if (count != 0) return count; + } + } + method = null; + return 0; + } + + static IEnumerable<Type> SelfAndBaseTypes(Type type) + { + if (type.IsInterface) + { + List<Type> types = new List<Type>(); + AddInterface(types, type); + return types; + } + return SelfAndBaseClasses(type); + } + + static IEnumerable<Type> SelfAndBaseClasses(Type type) + { + while (type != null) + { + yield return type; + type = type.BaseType; + } + } + + static void AddInterface(List<Type> types, Type type) + { + if (!types.Contains(type)) + { + types.Add(type); + foreach (Type t in type.GetInterfaces()) AddInterface(types, t); + } + } + + class MethodData + { + public MethodBase MethodBase; + public ParameterInfo[] Parameters; + public Expression[] Args; + } + + int FindBestMethod(IEnumerable<MethodBase> methods, Expression[] args, out MethodBase method) + { + MethodData[] applicable = methods. + Select(m => new MethodData { MethodBase = m, Parameters = m.GetParameters() }). + Where(m => IsApplicable(m, args)). + ToArray(); + if (applicable.Length > 1) + { + applicable = applicable. + Where(m => applicable.All(n => m == n || IsBetterThan(args, m, n))). + ToArray(); + } + if (applicable.Length == 1) + { + MethodData md = applicable[0]; + for (int i = 0; i < args.Length; i++) args[i] = md.Args[i]; + method = md.MethodBase; + } + else + { + method = null; + } + return applicable.Length; + } + + bool IsApplicable(MethodData method, Expression[] args) + { + if (method.Parameters.Length != args.Length) return false; + Expression[] promotedArgs = new Expression[args.Length]; + for (int i = 0; i < args.Length; i++) + { + ParameterInfo pi = method.Parameters[i]; + if (pi.IsOut) return false; + Expression promoted = PromoteExpression(args[i], pi.ParameterType, false); + if (promoted == null) return false; + promotedArgs[i] = promoted; + } + method.Args = promotedArgs; + return true; + } + + Expression PromoteExpression(Expression expr, Type type, bool exact) + { + if (expr.Type == type) return expr; + if (expr is ConstantExpression) + { + ConstantExpression ce = (ConstantExpression)expr; + if (ce == nullLiteral) + { + if (!type.IsValueType || IsNullableType(type)) + return Expression.Constant(null, type); + } + else + { + string text; + if (literals.TryGetValue(ce, out text)) + { + Type target = GetNonNullableType(type); + Object value = null; + switch (Type.GetTypeCode(ce.Type)) + { + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + value = ParseNumber(text, target); + break; + case TypeCode.Double: + if (target == typeof(decimal)) value = ParseNumber(text, target); + break; + case TypeCode.String: + value = ParseEnum(text, target); + break; + } + if (value != null) + return Expression.Constant(value, type); + } + } + } + if (IsCompatibleWith(expr.Type, type)) + { + if (type.IsValueType || exact) return Expression.Convert(expr, type); + return expr; + } + return null; + } + + static object ParseNumber(string text, Type type) + { + switch (Type.GetTypeCode(GetNonNullableType(type))) + { + case TypeCode.SByte: + sbyte sb; + if (sbyte.TryParse(text, out sb)) return sb; + break; + case TypeCode.Byte: + byte b; + if (byte.TryParse(text, out b)) return b; + break; + case TypeCode.Int16: + short s; + if (short.TryParse(text, out s)) return s; + break; + case TypeCode.UInt16: + ushort us; + if (ushort.TryParse(text, out us)) return us; + break; + case TypeCode.Int32: + int i; + if (int.TryParse(text, out i)) return i; + break; + case TypeCode.UInt32: + uint ui; + if (uint.TryParse(text, out ui)) return ui; + break; + case TypeCode.Int64: + long l; + if (long.TryParse(text, out l)) return l; + break; + case TypeCode.UInt64: + ulong ul; + if (ulong.TryParse(text, out ul)) return ul; + break; + case TypeCode.Single: + float f; + if (float.TryParse(text, out f)) return f; + break; + case TypeCode.Double: + double d; + if (double.TryParse(text, out d)) return d; + break; + case TypeCode.Decimal: + decimal e; + if (decimal.TryParse(text, out e)) return e; + break; + } + return null; + } + + static object ParseEnum(string name, Type type) + { + if (type.IsEnum) + { + MemberInfo[] memberInfos = type.FindMembers(MemberTypes.Field, + BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Static, + Type.FilterNameIgnoreCase, name); + if (memberInfos.Length != 0) return ((FieldInfo)memberInfos[0]).GetValue(null); + } + return null; + } + + static bool IsCompatibleWith(Type source, Type target) + { + if (source == target) return true; + if (!target.IsValueType) return target.IsAssignableFrom(source); + Type st = GetNonNullableType(source); + Type tt = GetNonNullableType(target); + if (st != source && tt == target) return false; + TypeCode sc = st.IsEnum ? TypeCode.Object : Type.GetTypeCode(st); + TypeCode tc = tt.IsEnum ? TypeCode.Object : Type.GetTypeCode(tt); + switch (sc) + { + case TypeCode.SByte: + switch (tc) + { + case TypeCode.SByte: + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Byte: + switch (tc) + { + case TypeCode.Byte: + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int16: + switch (tc) + { + case TypeCode.Int16: + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt16: + switch (tc) + { + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int32: + switch (tc) + { + case TypeCode.Int32: + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt32: + switch (tc) + { + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Int64: + switch (tc) + { + case TypeCode.Int64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.UInt64: + switch (tc) + { + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + return true; + } + break; + case TypeCode.Single: + switch (tc) + { + case TypeCode.Single: + case TypeCode.Double: + return true; + } + break; + default: + if (st == tt) return true; + break; + } + return false; + } + + static bool IsBetterThan(Expression[] args, MethodData m1, MethodData m2) + { + bool better = false; + for (int i = 0; i < args.Length; i++) + { + int c = CompareConversions(args[i].Type, + m1.Parameters[i].ParameterType, + m2.Parameters[i].ParameterType); + if (c < 0) return false; + if (c > 0) better = true; + } + return better; + } + + // Return 1 if s -> t1 is a better conversion than s -> t2 + // Return -1 if s -> t2 is a better conversion than s -> t1 + // Return 0 if neither conversion is better + static int CompareConversions(Type s, Type t1, Type t2) + { + if (t1 == t2) return 0; + if (s == t1) return 1; + if (s == t2) return -1; + bool t1t2 = IsCompatibleWith(t1, t2); + bool t2t1 = IsCompatibleWith(t2, t1); + if (t1t2 && !t2t1) return 1; + if (t2t1 && !t1t2) return -1; + if (IsSignedIntegralType(t1) && IsUnsignedIntegralType(t2)) return 1; + if (IsSignedIntegralType(t2) && IsUnsignedIntegralType(t1)) return -1; + return 0; + } + + Expression GenerateEqual(Expression left, Expression right) + { + return Expression.Equal(left, right); + } + + Expression GenerateNotEqual(Expression left, Expression right) + { + return Expression.NotEqual(left, right); + } + + Expression GenerateGreaterThan(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.GreaterThan( + GenerateStaticMethodCall("Compare", left, right), + Expression.Constant(0) + ); + } + return Expression.GreaterThan(left, right); + } + + Expression GenerateGreaterThanEqual(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.GreaterThanOrEqual( + GenerateStaticMethodCall("Compare", left, right), + Expression.Constant(0) + ); + } + return Expression.GreaterThanOrEqual(left, right); + } + + Expression GenerateLessThan(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.LessThan( + GenerateStaticMethodCall("Compare", left, right), + Expression.Constant(0) + ); + } + return Expression.LessThan(left, right); + } + + Expression GenerateLessThanEqual(Expression left, Expression right) + { + if (left.Type == typeof(string)) + { + return Expression.LessThanOrEqual( + GenerateStaticMethodCall("Compare", left, right), + Expression.Constant(0) + ); + } + return Expression.LessThanOrEqual(left, right); + } + + Expression GenerateAdd(Expression left, Expression right) + { + if (left.Type == typeof(string) && right.Type == typeof(string)) + { + return GenerateStaticMethodCall("Concat", left, right); + } + return Expression.Add(left, right); + } + + Expression GenerateSubtract(Expression left, Expression right) + { + return Expression.Subtract(left, right); + } + + Expression GenerateStringConcat(Expression left, Expression right) + { + return Expression.Call( + null, + typeof(string).GetMethod("Concat", new[] { typeof(object), typeof(object) }), + new[] { left, right }); + } + + MethodInfo GetStaticMethod(string methodName, Expression left, Expression right) + { + return left.Type.GetMethod(methodName, new[] { left.Type, right.Type }); + } + + Expression GenerateStaticMethodCall(string methodName, Expression left, Expression right) + { + return Expression.Call(null, GetStaticMethod(methodName, left, right), new[] { left, right }); + } + + void SetTextPos(int pos) + { + textPos = pos; + ch = textPos < textLen ? text[textPos] : '\0'; + } + + void NextChar() + { + if (textPos < textLen) textPos++; + ch = textPos < textLen ? text[textPos] : '\0'; + } + + void NextToken() + { + while (Char.IsWhiteSpace(ch)) NextChar(); + TokenId t; + int tokenPos = textPos; + 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 '\'': + char 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; + } + throw ParseError(textPos, Res.InvalidCharacter, ch); + } + token.id = t; + token.text = text.Substring(tokenPos, textPos - tokenPos); + token.pos = tokenPos; + } + + bool TokenIdentifierIs(string id) + { + return token.id == TokenId.Identifier && String.Equals(id, token.text, StringComparison.OrdinalIgnoreCase); + } + + string GetIdentifier() + { + ValidateToken(TokenId.Identifier, Res.IdentifierExpected); + string id = token.text; + if (id.Length > 1 && id[0] == '@') id = id.Substring(1); + return id; + } + + void ValidateDigit() + { + if (!Char.IsDigit(ch)) throw ParseError(textPos, Res.DigitExpected); + } + + void ValidateToken(TokenId t, string errorMessage) + { + if (token.id != t) throw ParseError(errorMessage); + } + + void ValidateToken(TokenId t) + { + if (token.id != t) throw ParseError(Res.SyntaxError); + } + + Exception ParseError(string format, params object[] args) + { + return ParseError(token.pos, format, args); + } + + Exception ParseError(int pos, string format, params object[] args) + { + return new ParseException(string.Format(System.Globalization.CultureInfo.CurrentCulture, format, args), pos); + } + + static Dictionary<string, object> CreateKeywords() + { + Dictionary<string, object> d = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); + d.Add("true", trueLiteral); + d.Add("false", falseLiteral); + d.Add("null", nullLiteral); + d.Add(keywordIt, keywordIt); + d.Add(keywordIif, keywordIif); + d.Add(keywordNew, keywordNew); + foreach (Type type in predefinedTypes) d.Add(type.Name, type); + return d; + } + } + + static class Res + { + 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"; + } +}