You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
2545 lines
68 KiB
C#
2545 lines
68 KiB
C#
//Copyright (C) Microsoft Corporation. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Linq;
|
|
using System.Linq.Expressions;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Runtime.Serialization;
|
|
using System.Threading;
|
|
|
|
namespace System.Linq.Dynamic
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public static class DynamicQueryable
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="source"></param>
|
|
/// <param name="predicate"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
public static IQueryable<T> Where<T>( this IQueryable<T> source, string predicate, params object[] values )
|
|
{
|
|
return ( IQueryable<T> )Where( ( IQueryable )source, predicate, values );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="predicate"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
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 ) ) );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="selector"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
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 ) ) );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <param name="source"></param>
|
|
/// <param name="ordering"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
public static IQueryable<T> OrderBy<T>( this IQueryable<T> source, string ordering, params object[] values )
|
|
{
|
|
return ( IQueryable<T> )OrderBy( ( IQueryable )source, ordering, values );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="ordering"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
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 );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="count"></param>
|
|
/// <returns></returns>
|
|
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 ) ) );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="count"></param>
|
|
/// <returns></returns>
|
|
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 ) ) );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <param name="keySelector"></param>
|
|
/// <param name="elementSelector"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
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 ) ) );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <returns></returns>
|
|
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 ) );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="source"></param>
|
|
/// <returns></returns>
|
|
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 ) );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public abstract class DynamicClass
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public class DynamicProperty
|
|
{
|
|
string name;
|
|
Type type;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="name"></param>
|
|
/// <param name="type"></param>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public string Name
|
|
{
|
|
get { return name; }
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public Type Type
|
|
{
|
|
get { return type; }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public static class DynamicExpression
|
|
{
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="resultType"></param>
|
|
/// <param name="expression"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
public static Expression Parse( Type resultType, string expression, params object[] values )
|
|
{
|
|
ExpressionParser parser = new ExpressionParser( null, expression, values );
|
|
return parser.Parse( resultType );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="itType"></param>
|
|
/// <param name="resultType"></param>
|
|
/// <param name="expression"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
public static LambdaExpression ParseLambda( Type itType, Type resultType, string expression, params object[] values )
|
|
{
|
|
return ParseLambda( new ParameterExpression[] { Expression.Parameter( itType, "" ) }, resultType, expression, values );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="parameters"></param>
|
|
/// <param name="resultType"></param>
|
|
/// <param name="expression"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
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 );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <typeparam name="T"></typeparam>
|
|
/// <typeparam name="S"></typeparam>
|
|
/// <param name="expression"></param>
|
|
/// <param name="values"></param>
|
|
/// <returns></returns>
|
|
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 );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="properties"></param>
|
|
/// <returns></returns>
|
|
public static Type CreateClass( params DynamicProperty[] properties )
|
|
{
|
|
return ClassFactory.Instance.GetDynamicClass( properties );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="properties"></param>
|
|
/// <returns></returns>
|
|
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 );
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
[Serializable]
|
|
public sealed class ParseException : Exception
|
|
{
|
|
int position;
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="message"></param>
|
|
/// <param name="position"></param>
|
|
public ParseException( string message, int position )
|
|
: base( message )
|
|
{
|
|
this.position = position;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public int Position
|
|
{
|
|
get { return position; }
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public override string ToString()
|
|
{
|
|
return string.Format( Res.ParseExceptionFormat, Message, position );
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="Info"></param>
|
|
/// <param name="Context"></param>
|
|
public override void GetObjectData( SerializationInfo Info, StreamingContext Context )
|
|
{
|
|
base.GetObjectData( Info, Context );
|
|
}
|
|
}
|
|
|
|
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";
|
|
}
|
|
}
|