modernuo/Projects/UOContent/Commands/Generic/Extensions/Compilers/ConditionalCompiler.cs

636 lines
18 KiB
C#
Raw Permalink Normal View History

2020-08-27 18:30:38 -07:00
using System;
using System.Globalization;
using System.Reflection;
using System.Reflection.Emit;
namespace Server.Commands.Generic
{
public interface IConditional
{
bool Verify(object obj);
}
public interface ICondition
{
// Invoked during the constructor
void Construct(TypeBuilder typeBuilder, ILGenerator il, int index);
// Target object will be loaded on the stack
void Compile(MethodEmitter emitter);
}
public sealed class TypeCondition : ICondition
{
public static TypeCondition Default = new();
2020-08-27 18:30:38 -07:00
void ICondition.Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
{
}
void ICondition.Compile(MethodEmitter emitter)
{
// The object was safely cast to be the conditionals type
// If it's null, then the type cast didn't work...
emitter.LoadNull();
emitter.Compare(OpCodes.Ceq);
emitter.LogicalNot();
}
}
public sealed class PropertyValue
{
public PropertyValue(Type type, object value)
{
Type = type;
Value = value;
}
public Type Type { get; }
public object Value { get; private set; }
public FieldInfo Field { get; private set; }
public bool HasField => Field != null;
public void Load(MethodEmitter method)
{
if (Field != null)
{
method.LoadArgument(0);
method.LoadField(Field);
}
else if (Value == null)
{
method.LoadNull(Type);
}
else
{
if (Value is int i)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
method.Load(i);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
else if (Value is long l)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
method.Load(l);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
else if (Value is float f)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
method.Load(f);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
else if (Value is double d)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
method.Load(d);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
else if (Value is char c)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
method.Load(c);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
else if (Value is bool b)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
method.Load(b);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
else if (Value is string s)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
method.Load(s);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
else if (Value is Enum e)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
method.Load(e);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
else
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
throw new InvalidOperationException("Unrecognized comparison value.");
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
}
}
public void Acquire(TypeBuilder typeBuilder, ILGenerator il, string fieldName)
{
2021-08-03 00:34:44 -07:00
if (Value is not string toParse)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
return;
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
if (!Type.IsValueType && toParse == "null")
{
Value = null;
}
else if (Type == typeof(string))
{
if (toParse == @"@""null""")
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
toParse = "null";
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
Value = toParse;
}
else if (Type.IsEnum)
{
Value = Enum.Parse(Type, toParse, true);
}
else if (Type == typeof(bool))
{
Value = bool.Parse(toParse);
}
2020-08-27 18:30:38 -07:00
else
{
MethodInfo parseMethod;
object[] parseArgs;
var parseNumber = Type.GetMethod(
"Parse",
BindingFlags.Public | BindingFlags.Static,
null,
Types.ParseStringNumericParamTypes,
2020-08-27 18:30:38 -07:00
null
);
if (parseNumber != null)
{
var style = NumberStyles.Integer;
if (toParse.InsensitiveStartsWith("0x"))
2020-08-27 18:30:38 -07:00
{
style = NumberStyles.HexNumber;
toParse = toParse[2..];
2020-08-27 18:30:38 -07:00
}
parseMethod = parseNumber;
parseArgs = new object[] { toParse, style };
}
else
{
var parseGeneral = Type.GetMethod(
"Parse",
BindingFlags.Public | BindingFlags.Static,
null,
Types.ParseStringParamTypes,
2020-08-27 18:30:38 -07:00
null
);
parseMethod = parseGeneral;
parseArgs = new object[] { toParse, null };
2020-08-27 18:30:38 -07:00
}
if (parseMethod != null)
{
Value = parseMethod.Invoke(null, parseArgs);
if (!Type.IsPrimitive)
{
Field = typeBuilder.DefineField(
fieldName,
Type,
FieldAttributes.Private | FieldAttributes.InitOnly
);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldstr, toParse);
if (parseArgs.Length == 2) // dirty evil hack :-(
2020-09-13 21:49:46 -07:00
{
if (parseArgs[1]?.GetType() == typeof(NumberStyles))
{
il.Emit(OpCodes.Ldc_I4, (int)parseArgs[1]);
}
else
{
// IFormatProvider for `IParsable<T>.Parse()` method.
il.Emit(OpCodes.Ldnull);
}
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
il.Emit(OpCodes.Call, parseMethod);
il.Emit(OpCodes.Stfld, Field);
}
}
else
{
throw new InvalidOperationException(
$"Unable to convert string \"{Value}\" into type '{Type}'."
);
}
}
}
}
public abstract class PropertyCondition : ICondition
{
protected bool m_Not;
protected Property m_Property;
public PropertyCondition(Property property, bool not)
{
m_Property = property;
m_Not = not;
}
public abstract void Construct(TypeBuilder typeBuilder, ILGenerator il, int index);
public abstract void Compile(MethodEmitter emitter);
}
public enum StringOperator
{
Equal,
NotEqual,
Contains,
StartsWith,
EndsWith
}
public sealed class StringCondition : PropertyCondition
{
private readonly bool m_IgnoreCase;
private readonly StringOperator m_Operator;
private readonly PropertyValue m_Value;
public StringCondition(Property property, bool not, StringOperator op, object value, bool ignoreCase)
: base(property, not)
{
m_Operator = op;
m_Value = new PropertyValue(property.Type, value);
m_IgnoreCase = ignoreCase;
}
public override void Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
{
m_Value.Acquire(typeBuilder, il, $"v{index}");
}
public override void Compile(MethodEmitter emitter)
{
var inverse = false;
Type type = m_IgnoreCase ? typeof(InsensitiveStringHelpers) : typeof(OrdinalStringHelpers);
2020-08-27 18:30:38 -07:00
string methodName;
switch (m_Operator)
{
case StringOperator.NotEqual:
{
inverse = true;
goto case StringOperator.Equal;
}
case StringOperator.Equal:
{
if (m_IgnoreCase)
{
methodName = "InsensitiveEquals";
}
else
{
methodName = "EqualsOrdinal";
}
break;
}
2020-08-27 18:30:38 -07:00
case StringOperator.Contains:
{
if (m_IgnoreCase)
{
methodName = "InsensitiveContains";
}
else
{
methodName = "ContainsOrdinal";
}
break;
}
2020-08-27 18:30:38 -07:00
case StringOperator.StartsWith:
{
if (m_IgnoreCase)
{
methodName = "InsensitiveStartsWith";
}
else
{
methodName = "StartsWithOrdinal";
}
break;
}
2020-08-27 18:30:38 -07:00
case StringOperator.EndsWith:
{
if (m_IgnoreCase)
{
methodName = "InsensitiveEndsWith";
}
else
{
methodName = "EndsWithOrdinal";
}
break;
}
2020-08-27 18:30:38 -07:00
default:
{
throw new InvalidOperationException("Invalid string comparison operator.");
}
2020-08-27 18:30:38 -07:00
}
if (m_Operator is StringOperator.Equal or StringOperator.NotEqual)
2020-08-27 18:30:38 -07:00
{
emitter.BeginCall(
type.GetMethod(
methodName,
BindingFlags.Public | BindingFlags.Static,
null,
new[]
{
typeof(string),
typeof(string)
},
null
)
);
emitter.Chain(m_Property);
m_Value.Load(emitter);
emitter.FinishCall();
}
else
{
var notNull = emitter.CreateLabel();
var moveOn = emitter.CreateLabel();
var temp = emitter.AcquireTemp(m_Property.Type);
emitter.Chain(m_Property);
emitter.StoreLocal(temp);
emitter.LoadLocal(temp);
emitter.BranchIfTrue(notNull);
emitter.Load(false);
emitter.Pop();
emitter.Branch(moveOn);
emitter.MarkLabel(notNull);
emitter.LoadLocal(temp);
emitter.BeginCall(
type.GetMethod(
2020-08-27 18:30:38 -07:00
methodName,
BindingFlags.Public | BindingFlags.Instance,
null,
new[]
{
typeof(string)
},
null
)
);
m_Value.Load(emitter);
emitter.FinishCall();
emitter.MarkLabel(moveOn);
}
if (m_Not != inverse)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
emitter.LogicalNot();
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
}
}
public enum ComparisonOperator
{
Equal,
NotEqual,
Greater,
GreaterEqual,
Lesser,
LesserEqual
}
public sealed class ComparisonCondition : PropertyCondition
{
private readonly ComparisonOperator m_Operator;
private readonly PropertyValue m_Value;
public ComparisonCondition(Property property, bool not, ComparisonOperator op, object value)
: base(property, not)
{
m_Operator = op;
m_Value = new PropertyValue(property.Type, value);
}
public override void Construct(TypeBuilder typeBuilder, ILGenerator il, int index)
{
m_Value.Acquire(typeBuilder, il, $"v{index}");
}
public override void Compile(MethodEmitter emitter)
{
emitter.Chain(m_Property);
var inverse = false;
var couldCompare =
emitter.CompareTo(1, () => { m_Value.Load(emitter); });
if (couldCompare)
{
emitter.Load(0);
switch (m_Operator)
{
case ComparisonOperator.Equal:
{
emitter.Compare(OpCodes.Ceq);
break;
}
2020-08-27 18:30:38 -07:00
case ComparisonOperator.NotEqual:
{
emitter.Compare(OpCodes.Ceq);
inverse = true;
break;
}
2020-08-27 18:30:38 -07:00
case ComparisonOperator.Greater:
{
emitter.Compare(OpCodes.Cgt);
break;
}
2020-08-27 18:30:38 -07:00
case ComparisonOperator.GreaterEqual:
{
emitter.Compare(OpCodes.Clt);
inverse = true;
break;
}
2020-08-27 18:30:38 -07:00
case ComparisonOperator.Lesser:
{
emitter.Compare(OpCodes.Clt);
break;
}
2020-08-27 18:30:38 -07:00
case ComparisonOperator.LesserEqual:
{
emitter.Compare(OpCodes.Cgt);
inverse = true;
break;
}
2020-08-27 18:30:38 -07:00
default:
{
throw new InvalidOperationException("Invalid comparison operator.");
}
2020-08-27 18:30:38 -07:00
}
}
else
{
// This type is -not- comparable
// We can only support == and != operations
m_Value.Load(emitter);
switch (m_Operator)
{
case ComparisonOperator.Equal:
{
emitter.Compare(OpCodes.Ceq);
break;
}
2020-08-27 18:30:38 -07:00
case ComparisonOperator.NotEqual:
{
emitter.Compare(OpCodes.Ceq);
inverse = true;
break;
}
2020-08-27 18:30:38 -07:00
case ComparisonOperator.Greater:
case ComparisonOperator.GreaterEqual:
case ComparisonOperator.Lesser:
case ComparisonOperator.LesserEqual:
{
throw new InvalidOperationException("Property does not support relational comparisons.");
}
2020-08-27 18:30:38 -07:00
default:
{
throw new InvalidOperationException("Invalid operator.");
}
2020-08-27 18:30:38 -07:00
}
}
if (m_Not != inverse)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
emitter.LogicalNot();
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
}
}
public static class ConditionalCompiler
{
public static IConditional Compile(AssemblyEmitter assembly, Type objectType, ICondition[] conditions, int index)
{
var typeBuilder = assembly.DefineType(
$"__conditional{index}",
TypeAttributes.Public,
typeof(object)
);
{
var ctor = typeBuilder.DefineConstructor(
MethodAttributes.Public,
CallingConventions.Standard,
Type.EmptyTypes
);
var il = ctor.GetILGenerator();
// : base()
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(object).GetConstructor(Type.EmptyTypes));
for (var i = 0; i < conditions.Length; ++i)
2020-09-13 21:49:46 -07:00
{
2020-08-27 18:30:38 -07:00
conditions[i].Construct(typeBuilder, il, i);
2020-09-13 21:49:46 -07:00
}
2020-08-27 18:30:38 -07:00
// return;
il.Emit(OpCodes.Ret);
}
typeBuilder.AddInterfaceImplementation(typeof(IConditional));
MethodBuilder compareMethod;
{
var emitter = new MethodEmitter(typeBuilder);
emitter.Define(
/* name */ "Verify",
/* attr */
MethodAttributes.Public | MethodAttributes.Virtual,
/* return */
typeof(bool),
/* params */
new[] { typeof(object) }
);
var obj = emitter.CreateLocal(objectType);
var eq = emitter.CreateLocal(typeof(bool));
emitter.LoadArgument(1);
emitter.CastAs(objectType);
emitter.StoreLocal(obj);
var done = emitter.CreateLabel();
for (var i = 0; i < conditions.Length; ++i)
{
if (i > 0)
{
emitter.LoadLocal(eq);
emitter.BranchIfFalse(done);
}
emitter.LoadLocal(obj);
conditions[i].Compile(emitter);
emitter.StoreLocal(eq);
}
emitter.MarkLabel(done);
emitter.LoadLocal(eq);
emitter.Return();
typeBuilder.DefineMethodOverride(
emitter.Method,
typeof(IConditional).GetMethod(
"Verify",
new[]
{
typeof(object)
}
)
);
compareMethod = emitter.Method;
}
var conditionalType = typeBuilder.CreateType();
return conditionalType.CreateInstance<IConditional>();
2020-08-27 18:30:38 -07:00
}
}
}