mirror of
https://github.com/modernuo/ModernUO
synced 2026-08-11 22:23:06 -04:00
635 lines
18 KiB
C#
635 lines
18 KiB
C#
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();
|
|
|
|
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)
|
|
{
|
|
method.Load(i);
|
|
}
|
|
else if (Value is long l)
|
|
{
|
|
method.Load(l);
|
|
}
|
|
else if (Value is float f)
|
|
{
|
|
method.Load(f);
|
|
}
|
|
else if (Value is double d)
|
|
{
|
|
method.Load(d);
|
|
}
|
|
else if (Value is char c)
|
|
{
|
|
method.Load(c);
|
|
}
|
|
else if (Value is bool b)
|
|
{
|
|
method.Load(b);
|
|
}
|
|
else if (Value is string s)
|
|
{
|
|
method.Load(s);
|
|
}
|
|
else if (Value is Enum e)
|
|
{
|
|
method.Load(e);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException("Unrecognized comparison value.");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Acquire(TypeBuilder typeBuilder, ILGenerator il, string fieldName)
|
|
{
|
|
if (Value is not string toParse)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!Type.IsValueType && toParse == "null")
|
|
{
|
|
Value = null;
|
|
}
|
|
else if (Type == typeof(string))
|
|
{
|
|
if (toParse == @"@""null""")
|
|
{
|
|
toParse = "null";
|
|
}
|
|
|
|
Value = toParse;
|
|
}
|
|
else if (Type.IsEnum)
|
|
{
|
|
Value = Enum.Parse(Type, toParse, true);
|
|
}
|
|
else if (Type == typeof(bool))
|
|
{
|
|
Value = bool.Parse(toParse);
|
|
}
|
|
else
|
|
{
|
|
MethodInfo parseMethod;
|
|
object[] parseArgs;
|
|
|
|
var parseNumber = Type.GetMethod(
|
|
"Parse",
|
|
BindingFlags.Public | BindingFlags.Static,
|
|
null,
|
|
Types.ParseStringNumericParamTypes,
|
|
null
|
|
);
|
|
|
|
if (parseNumber != null)
|
|
{
|
|
var style = NumberStyles.Integer;
|
|
|
|
if (toParse.InsensitiveStartsWith("0x"))
|
|
{
|
|
style = NumberStyles.HexNumber;
|
|
toParse = toParse[2..];
|
|
}
|
|
|
|
parseMethod = parseNumber;
|
|
parseArgs = new object[] { toParse, style };
|
|
}
|
|
else
|
|
{
|
|
var parseGeneral = Type.GetMethod(
|
|
"Parse",
|
|
BindingFlags.Public | BindingFlags.Static,
|
|
null,
|
|
Types.ParseStringParamTypes,
|
|
null
|
|
);
|
|
|
|
parseMethod = parseGeneral;
|
|
parseArgs = new object[] { toParse, null };
|
|
}
|
|
|
|
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 :-(
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
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);
|
|
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;
|
|
}
|
|
|
|
case StringOperator.Contains:
|
|
{
|
|
if (m_IgnoreCase)
|
|
{
|
|
methodName = "InsensitiveContains";
|
|
}
|
|
else
|
|
{
|
|
methodName = "ContainsOrdinal";
|
|
}
|
|
break;
|
|
}
|
|
|
|
case StringOperator.StartsWith:
|
|
{
|
|
if (m_IgnoreCase)
|
|
{
|
|
methodName = "InsensitiveStartsWith";
|
|
}
|
|
else
|
|
{
|
|
methodName = "StartsWithOrdinal";
|
|
}
|
|
break;
|
|
}
|
|
|
|
case StringOperator.EndsWith:
|
|
{
|
|
if (m_IgnoreCase)
|
|
{
|
|
methodName = "InsensitiveEndsWith";
|
|
}
|
|
else
|
|
{
|
|
methodName = "EndsWithOrdinal";
|
|
}
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new InvalidOperationException("Invalid string comparison operator.");
|
|
}
|
|
}
|
|
|
|
if (m_Operator is StringOperator.Equal or StringOperator.NotEqual)
|
|
{
|
|
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(
|
|
methodName,
|
|
BindingFlags.Public | BindingFlags.Instance,
|
|
null,
|
|
new[]
|
|
{
|
|
typeof(string)
|
|
},
|
|
null
|
|
)
|
|
);
|
|
|
|
m_Value.Load(emitter);
|
|
|
|
emitter.FinishCall();
|
|
|
|
emitter.MarkLabel(moveOn);
|
|
}
|
|
|
|
if (m_Not != inverse)
|
|
{
|
|
emitter.LogicalNot();
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
case ComparisonOperator.NotEqual:
|
|
{
|
|
emitter.Compare(OpCodes.Ceq);
|
|
inverse = true;
|
|
break;
|
|
}
|
|
|
|
case ComparisonOperator.Greater:
|
|
{
|
|
emitter.Compare(OpCodes.Cgt);
|
|
break;
|
|
}
|
|
|
|
case ComparisonOperator.GreaterEqual:
|
|
{
|
|
emitter.Compare(OpCodes.Clt);
|
|
inverse = true;
|
|
break;
|
|
}
|
|
|
|
case ComparisonOperator.Lesser:
|
|
{
|
|
emitter.Compare(OpCodes.Clt);
|
|
break;
|
|
}
|
|
|
|
case ComparisonOperator.LesserEqual:
|
|
{
|
|
emitter.Compare(OpCodes.Cgt);
|
|
inverse = true;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new InvalidOperationException("Invalid comparison operator.");
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
case ComparisonOperator.NotEqual:
|
|
{
|
|
emitter.Compare(OpCodes.Ceq);
|
|
inverse = true;
|
|
break;
|
|
}
|
|
|
|
case ComparisonOperator.Greater:
|
|
case ComparisonOperator.GreaterEqual:
|
|
case ComparisonOperator.Lesser:
|
|
case ComparisonOperator.LesserEqual:
|
|
{
|
|
throw new InvalidOperationException("Property does not support relational comparisons.");
|
|
}
|
|
|
|
default:
|
|
{
|
|
throw new InvalidOperationException("Invalid operator.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (m_Not != inverse)
|
|
{
|
|
emitter.LogicalNot();
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
conditions[i].Construct(typeBuilder, il, i);
|
|
}
|
|
|
|
// 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>();
|
|
}
|
|
}
|
|
}
|