From 78abcaee8e697a01ed6a0b81a8d357fc8d5feb80 Mon Sep 17 00:00:00 2001
From: InsanityCode <56584762+InsanityCode@users.noreply.github.com>
Date: Thu, 28 Mar 2024 16:49:00 +0100
Subject: [PATCH 1/2] add AdvancedDebuggerDisplayExtensions
---
ChaosUtil.Debug.csproj | 1 +
.../AdvancedDebuggerDisplay/EvaluatedToken.cs | 40 ++++++++
.../AdvancedDebuggerDisplay/Members/Field.cs | 31 ++++++
.../Members/MemberMap.cs | 28 ++++++
.../AdvancedDebuggerDisplay/Members/Method.cs | 98 +++++++++++++++++++
.../Members/Property.cs | 31 ++++++
.../AdvancedDebuggerDisplay/TerminalToken.cs | 12 +++
source/AdvancedDebuggerDisplay/Token.cs | 16 +++
source/AdvancedDebuggerDisplayExtensions.cs | 84 ++++++++++++++++
9 files changed, 341 insertions(+)
create mode 100644 source/AdvancedDebuggerDisplay/EvaluatedToken.cs
create mode 100644 source/AdvancedDebuggerDisplay/Members/Field.cs
create mode 100644 source/AdvancedDebuggerDisplay/Members/MemberMap.cs
create mode 100644 source/AdvancedDebuggerDisplay/Members/Method.cs
create mode 100644 source/AdvancedDebuggerDisplay/Members/Property.cs
create mode 100644 source/AdvancedDebuggerDisplay/TerminalToken.cs
create mode 100644 source/AdvancedDebuggerDisplay/Token.cs
create mode 100644 source/AdvancedDebuggerDisplayExtensions.cs
diff --git a/ChaosUtil.Debug.csproj b/ChaosUtil.Debug.csproj
index 37ec86c..a9117ea 100644
--- a/ChaosUtil.Debug.csproj
+++ b/ChaosUtil.Debug.csproj
@@ -23,6 +23,7 @@
+
diff --git a/source/AdvancedDebuggerDisplay/EvaluatedToken.cs b/source/AdvancedDebuggerDisplay/EvaluatedToken.cs
new file mode 100644
index 0000000..b69ab17
--- /dev/null
+++ b/source/AdvancedDebuggerDisplay/EvaluatedToken.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Linq;
+
+namespace ChaosUtil.Debug.AdvancedDebuggerDisplay
+{
+ class EvaluatedToken : Token
+ {
+ static string Trim(string str) => str.Trim();
+
+ readonly Type targetType;
+ readonly object obj;
+
+ public EvaluatedToken(object obj, string token)
+ : base(token)
+ {
+ Type targetType = obj.GetType();
+ foreach (string member in token.Split('.').Select(Trim))
+ {
+ // TODO: support indexers
+
+ if (Members.Field.GetValue(ref targetType, ref obj, member))
+ continue;
+
+ if (Members.Property.GetValue(ref targetType, ref obj, member))
+ continue;
+
+ if (Members.Method.GetValue(ref targetType, ref obj, member))
+ continue;
+
+ throw new Exception($"Could not evaluate \"{member}\".");
+ }
+
+ this.targetType = targetType;
+ this.obj = obj;
+ }
+
+ public override string Value()
+ => AdvancedDebuggerDisplayExtensions.DebugDisplayInternal(targetType, obj);
+ }
+}
diff --git a/source/AdvancedDebuggerDisplay/Members/Field.cs b/source/AdvancedDebuggerDisplay/Members/Field.cs
new file mode 100644
index 0000000..50549bb
--- /dev/null
+++ b/source/AdvancedDebuggerDisplay/Members/Field.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Reflection;
+using FieldMap = System.Collections.Generic.Dictionary<
+ System.Type,
+ System.Collections.Generic.Dictionary<
+ string,
+ System.Reflection.FieldInfo
+ >
+ >;
+
+namespace ChaosUtil.Debug.AdvancedDebuggerDisplay.Members
+{
+ static class Field
+ {
+ static readonly FieldMap fieldMap = new FieldMap();
+
+ public static bool GetValue(ref Type targetType, ref object obj, string fieldName)
+ {
+ Type t = targetType;
+ FieldInfo field = MemberMap.GetMember(fieldMap, targetType, fieldName, flags => t.GetField(fieldName, flags));
+ if (field != null)
+ {
+ obj = field.GetValue(obj);
+ targetType = field.FieldType;
+ return true;
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/source/AdvancedDebuggerDisplay/Members/MemberMap.cs b/source/AdvancedDebuggerDisplay/Members/MemberMap.cs
new file mode 100644
index 0000000..0764aba
--- /dev/null
+++ b/source/AdvancedDebuggerDisplay/Members/MemberMap.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Reflection;
+using SysCol = System.Collections.Generic;
+
+namespace ChaosUtil.Debug.AdvancedDebuggerDisplay.Members
+{
+ static class MemberMap
+ {
+ public static Member GetMember(
+ SysCol.Dictionary> memberMap,
+ Type type,
+ string memberName,
+ Func getMember
+ )
+ where Member : class
+ {
+ SysCol.Dictionary typeMemberMap;
+ if (!memberMap.TryGetValue(type, out typeMemberMap))
+ memberMap[type] = typeMemberMap = new SysCol.Dictionary();
+
+ Member member;
+ if (!typeMemberMap.TryGetValue(memberName, out member))
+ typeMemberMap[memberName] = member = getMember(BindingFlags.Public | BindingFlags.Instance) ?? getMember(BindingFlags.NonPublic | BindingFlags.Instance);
+
+ return member;
+ }
+ }
+}
diff --git a/source/AdvancedDebuggerDisplay/Members/Method.cs b/source/AdvancedDebuggerDisplay/Members/Method.cs
new file mode 100644
index 0000000..45c117c
--- /dev/null
+++ b/source/AdvancedDebuggerDisplay/Members/Method.cs
@@ -0,0 +1,98 @@
+using ChaosUtil.Primitives;
+using System;
+using System.Reflection;
+using MethodMap = System.Collections.Generic.Dictionary<
+ System.Type,
+ System.Collections.Generic.Dictionary<
+ string,
+ System.Reflection.MethodInfo
+ >
+ >;
+
+namespace ChaosUtil.Debug.AdvancedDebuggerDisplay.Members
+{
+ static class Method
+ {
+ static readonly MethodMap methodMap = new MethodMap();
+
+ static readonly MethodInfo debugDisplayMethod = typeof(AdvancedDebuggerDisplayExtensions).GetMethod(
+ nameof(AdvancedDebuggerDisplayExtensions.DebugDisplay),
+ BindingFlags.Public | BindingFlags.Static
+ );
+
+ public static bool GetValue(ref Type targetType, ref object obj, string member)
+ => GetMethodObject(ref targetType, ref obj, member)
+ || GetMethodInvocation(ref targetType, ref obj, member);
+
+ static MethodInfo FindMethod(Type targetType, string methodName, BindingFlags flags)
+ {
+ MethodInfo meth = targetType.GetMethod(methodName, flags, null, Array.empty, Array.empty);
+ if (meth != null)
+ return meth;
+
+ // TODO: properly support extension methods
+ if (methodName == nameof(AdvancedDebuggerDisplayExtensions.DebugDisplay))
+ return debugDisplayMethod.MakeGenericMethod(targetType);
+
+ return null;
+ }
+
+ public static bool GetMethodObject(ref Type targetType, ref object obj, string methodName)
+ {
+ Type t = targetType;
+ MethodInfo meth = MemberMap.GetMember(
+ methodMap,
+ targetType,
+ methodName,
+ flags => FindMethod(t, methodName, flags)
+ );
+ if (meth != null)
+ {
+ targetType = typeof(MethodInfo);
+ obj = meth;
+ return true;
+ }
+ else
+ return false;
+ }
+
+ public static bool GetMethodInvocation(ref Type targetType, ref object obj, string invocation)
+ {
+ if (invocation.Length < 3)
+ return false; // at least one letter for method name plus one pair of parantheses
+
+ int paraOpen = invocation.IndexOf('(');
+ if (paraOpen < 0)
+ return false;
+
+ int paraClose = invocation.LastIndexOf(')');
+ if (paraClose != invocation.Length - 1)
+ return false;
+
+ int argsLength = paraClose - paraOpen - 1;
+ if (argsLength != 0)
+ // TODO: support method arguments
+ throw new NotImplementedException("Currently only parameterless method calls are supported.");
+
+ string methodName = invocation.Substring(0, paraOpen);
+ Type t = targetType;
+ MethodInfo meth = MemberMap.GetMember(
+ methodMap,
+ targetType,
+ methodName,
+ flags => FindMethod(t, methodName, flags)
+ );
+ if (meth != null)
+ {
+ if (meth.IsStatic)
+ obj = meth.Invoke(null, new[] { obj });
+ else
+ obj = meth.Invoke(obj, Array