197 lines
6.8 KiB
C#
197 lines
6.8 KiB
C#
using System.Collections;
|
|
using System.Reflection.Emit;
|
|
using System.Reflection;
|
|
using Flee.ExpressionElements.Base;
|
|
|
|
using Flee.InternalTypes;
|
|
using Flee.PublicTypes;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
|
|
|
|
namespace Flee.ExpressionElements
|
|
{
|
|
internal class InElement : ExpressionElement
|
|
{
|
|
// Element we will search for
|
|
private ExpressionElement MyOperand;
|
|
// Elements we will compare against
|
|
private List<ExpressionElement> MyArguments;
|
|
// Collection to look in
|
|
private ExpressionElement MyTargetCollectionElement;
|
|
// Type of the collection
|
|
|
|
private Type MyTargetCollectionType;
|
|
// Initialize for searching a list of values
|
|
public InElement(ExpressionElement operand, IList listElements)
|
|
{
|
|
MyOperand = operand;
|
|
|
|
ExpressionElement[] arr = new ExpressionElement[listElements.Count];
|
|
listElements.CopyTo(arr, 0);
|
|
|
|
MyArguments = new List<ExpressionElement>(arr);
|
|
this.ResolveForListSearch();
|
|
}
|
|
|
|
// Initialize for searching a collection
|
|
public InElement(ExpressionElement operand, ExpressionElement targetCollection)
|
|
{
|
|
MyOperand = operand;
|
|
MyTargetCollectionElement = targetCollection;
|
|
this.ResolveForCollectionSearch();
|
|
}
|
|
|
|
private void ResolveForListSearch()
|
|
{
|
|
CompareElement ce = new CompareElement();
|
|
|
|
// Validate that our operand is comparable to all elements in the list
|
|
foreach (ExpressionElement argumentElement in MyArguments)
|
|
{
|
|
ce.Initialize(MyOperand, argumentElement, LogicalCompareOperation.Equal);
|
|
ce.Validate();
|
|
}
|
|
}
|
|
|
|
private void ResolveForCollectionSearch()
|
|
{
|
|
// Try to find a collection type
|
|
MyTargetCollectionType = this.GetTargetCollectionType();
|
|
|
|
if (MyTargetCollectionType == null)
|
|
{
|
|
base.ThrowCompileException("SearchArgIsNotKnownCollectionType", CompileExceptionReason.TypeMismatch, MyTargetCollectionElement.ResultType.Name);
|
|
}
|
|
|
|
// Validate that the operand type is compatible with the collection
|
|
MethodInfo mi = this.GetCollectionContainsMethod();
|
|
ParameterInfo p1 = mi.GetParameters()[0];
|
|
|
|
if (ImplicitConverter.EmitImplicitConvert(MyOperand.ResultType, p1.ParameterType, null) == false)
|
|
{
|
|
base.ThrowCompileException("OperandNotConvertibleToCollectionType", CompileExceptionReason.TypeMismatch, MyOperand.ResultType.Name, p1.ParameterType.Name);
|
|
}
|
|
}
|
|
|
|
private Type GetTargetCollectionType()
|
|
{
|
|
Type collType = MyTargetCollectionElement.ResultType;
|
|
|
|
// Try to see if the collection is a generic ICollection or IDictionary
|
|
Type[] interfaces = collType.GetInterfaces();
|
|
|
|
foreach (Type interfaceType in interfaces)
|
|
{
|
|
if (interfaceType.IsGenericType == false)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
Type genericTypeDef = interfaceType.GetGenericTypeDefinition();
|
|
|
|
if (object.ReferenceEquals(genericTypeDef, typeof(ICollection<>)) | object.ReferenceEquals(genericTypeDef, typeof(IDictionary<,>)))
|
|
{
|
|
return interfaceType;
|
|
}
|
|
}
|
|
|
|
// Try to see if it is a regular IList or IDictionary
|
|
if (typeof(IList<>).IsAssignableFrom(collType) == true)
|
|
{
|
|
return typeof(IList<>);
|
|
}
|
|
else if (typeof(IDictionary<,>).IsAssignableFrom(collType) == true)
|
|
{
|
|
return typeof(IDictionary<,>);
|
|
}
|
|
|
|
// Not a known collection type
|
|
return null;
|
|
}
|
|
|
|
public override void Emit(FleeILGenerator ilg, IServiceProvider services)
|
|
{
|
|
if ((MyTargetCollectionType != null))
|
|
{
|
|
this.EmitCollectionIn(ilg, services);
|
|
}
|
|
else
|
|
{
|
|
// Do the real emit
|
|
this.EmitListIn(ilg, services);
|
|
}
|
|
}
|
|
|
|
private void EmitCollectionIn(FleeILGenerator ilg, IServiceProvider services)
|
|
{
|
|
// Get the contains method
|
|
MethodInfo mi = this.GetCollectionContainsMethod();
|
|
ParameterInfo p1 = mi.GetParameters()[0];
|
|
|
|
// Load the collection
|
|
MyTargetCollectionElement.Emit(ilg, services);
|
|
// Load the argument
|
|
MyOperand.Emit(ilg, services);
|
|
// Do an implicit convert if necessary
|
|
ImplicitConverter.EmitImplicitConvert(MyOperand.ResultType, p1.ParameterType, ilg);
|
|
// Call the contains method
|
|
ilg.Emit(OpCodes.Callvirt, mi);
|
|
}
|
|
|
|
private MethodInfo GetCollectionContainsMethod()
|
|
{
|
|
string methodName = "Contains";
|
|
|
|
if (MyTargetCollectionType.IsGenericType == true && object.ReferenceEquals(MyTargetCollectionType.GetGenericTypeDefinition(), typeof(IDictionary<,>)))
|
|
{
|
|
methodName = "ContainsKey";
|
|
}
|
|
|
|
return MyTargetCollectionType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
|
}
|
|
|
|
private void EmitListIn(FleeILGenerator ilg, IServiceProvider services)
|
|
{
|
|
CompareElement ce = new CompareElement();
|
|
Label endLabel = ilg.DefineLabel();
|
|
Label trueTerminal = ilg.DefineLabel();
|
|
|
|
// Cache the operand since we will be comparing against it a lot
|
|
LocalBuilder lb = ilg.DeclareLocal(MyOperand.ResultType);
|
|
int targetIndex = lb.LocalIndex;
|
|
|
|
MyOperand.Emit(ilg, services);
|
|
Utility.EmitStoreLocal(ilg, targetIndex);
|
|
|
|
// Wrap our operand in a local shim
|
|
LocalBasedElement targetShim = new LocalBasedElement(MyOperand, targetIndex);
|
|
|
|
// Emit the compares
|
|
foreach (ExpressionElement argumentElement in MyArguments)
|
|
{
|
|
ce.Initialize(targetShim, argumentElement, LogicalCompareOperation.Equal);
|
|
ce.Emit(ilg, services);
|
|
|
|
EmitBranchToTrueTerminal(ilg, trueTerminal);
|
|
}
|
|
|
|
ilg.Emit(OpCodes.Ldc_I4_0);
|
|
ilg.Emit(OpCodes.Br_S, endLabel);
|
|
|
|
ilg.MarkLabel(trueTerminal);
|
|
|
|
ilg.Emit(OpCodes.Ldc_I4_1);
|
|
|
|
ilg.MarkLabel(endLabel);
|
|
}
|
|
|
|
private static void EmitBranchToTrueTerminal(FleeILGenerator ilg, Label trueTerminal)
|
|
{
|
|
ilg.EmitBranchTrue(trueTerminal);
|
|
}
|
|
|
|
public override System.Type ResultType => typeof(bool);
|
|
}
|
|
}
|