public static string? Parse()

in src/Microsoft.Diagnostics.Runtime/src/Common/DACNameParser.cs [33:277]


        public static string? Parse(string? name)
        {
            if(name == null)
                return null;

            if(name.Length == 0)
                return name;

            try
            {

                // This is the primary method of the parser. It operates as a simple state machine storing information about types encountered as
                // they are encountered and back-propagating information to types as it is discovered.
                //
                // The BEST way to debug/understand this method is to simply step through it while watching genericArgs and nameSegments in the debugger watch window. The TypeNameSegment
                // has a DebuggerDisplayAttribute so you should see the state of the type as it goes through transitions (i.e. going from simple to generic with a known count of args, as
                // its unknown args are filled in seeing them appear in the debugger display string as we patch in the actual type names, etc...)
                ParsingState currentState = ParsingState.ParsingTypeName;

                // The name segment lists hold types as we are constructing them. These can be complete types (such as System.String) or partial types (like List<T> before we have parsed what T is)
                // or nested types (like the +Entry part of Dictionary<TKey, TEntry>+Entry) which may be later unified with the type they are nested in (we represent parses like Dictionary`2+Entry
                // as two TypeNameSegments (Dictionary`2 and Entry) and unify them later since when we extract Dictionary we don't yet know its generic arity or the fact it is the outer type in
                // a nested type).
                //
                // The nameSegments relate to top-level type names, the genericArgs relate to generic argument lists as we are parsing them. The genericArgs entries are back-propagated both to earlier
                // generic args as well as to types in the nameSegments list, as we complete the argument list parsing.
                List<TypeNameSegment>? nameSegments = null;
                List<TypeNameSegment>? genericArgs = null;

                // Local helper methods to help minimize code duplication
                void EnsureNameSegmentList()
                {
                    if (nameSegments == null)
                        nameSegments = new List<TypeNameSegment>();
                }

                void EnsureGenericArgList()
                {
                    if (genericArgs == null)
                        genericArgs = new List<TypeNameSegment>();
                }

                int curPos = 0;
                int parsingNestedClassDepth = 0;
                int parsingGenericArgListDepth = 0;

                while (ShouldContinueParsing(currentState))
                {
                    switch (currentState)
                    {
                        case ParsingState.ParsingTypeName:
                            {
                                // We are parsing a type name, this is the initial state as well as one entered into every time we are extracting a single type name through the course of parsing.
                                // This handles parsing nested types as well as generic param types.
                                //
                                // Parsing of type names is non-extractive, i.e. we don't substring the input string or create any new heap allocations to track them (apart from the two local
                                // Lists), we simply remember the extents of the name (start/end).

                                int start;
                                (start, curPos) = GetTypeNameExtent(name, curPos, parsingGenericArgList: parsingGenericArgListDepth != 0);

                                // Special case: Check if after parsing the type name we have exhausted the string, if so it means the input string is the output string, so just return it
                                // without allocating a copy.
                                if (ReturnOriginalDACString(curPos, name.Length, nameSegments))
                                    return name;

                                bool typeIsNestedClass = (parsingNestedClassDepth != 0);
                                if (parsingGenericArgListDepth == 0)
                                {
                                    // We are parsing a top-level type name/sequence
                                    EnsureNameSegmentList();

#pragma warning disable CS8602 // EnsureNameSegmentList call above ensures that nameSegments is never null here
                                    nameSegments.Add(new TypeNameSegment(name, (start, curPos), typeIsNestedClass, parsingArgDepth: 0));
#pragma warning restore CS8602
                                }
                                else
                                {
                                    // We are parsing a generic list (potentialy nested lists in the case where a generic param is itself generic)
                                    EnsureGenericArgList();

#pragma warning disable CS8602 // EnsureGenericArgList call above ensures that genericArgs is never null here
                                    genericArgs.Add(new TypeNameSegment(name, (start, curPos), typeIsNestedClass, parsingGenericArgListDepth));
#pragma warning restore CS8602

                                }

                                if (parsingNestedClassDepth != 0)
                                    parsingNestedClassDepth--;

                                (currentState, curPos) = DetermineNextStateAndPos(name, curPos);
                                break;
                            }
                        case ParsingState.ParsingNestedClass:
                            {
                                // We are starting to parse a nested type name, just record the nested class depth (we have to handle multiple levels of nested classes), and 
                                // transition back to the type name parsing state.

                                parsingNestedClassDepth++;
                                currentState = ParsingState.ParsingTypeName;
                                break;
                            }
                        case ParsingState.ParsingGenericArgCount:
                            {
                                // Parse the arity of the generic type. Note: we do this 'in place' i.e. without extracting the count substring, it's unfortunate but int.Parse does not include
                                // an overload that operates in place based on start/length.

                                int genericArgCount;
                                (genericArgCount, curPos) = ParseGenericArityCountFromStringInPlace(name, curPos);

                                List<TypeNameSegment>? targetList = ((genericArgs != null) && (genericArgs.Count != 0)) ? genericArgs : nameSegments;

                                if (targetList != null)
                                {
                                    // NOTE: TypeNameSegment is a struct to avoid heap allocations, that means we have to extract / modify / re-store to ensure the updated state gets back into whatever
                                    // list this came from.
                                    int targetIndex = targetList.Count - 1;
                                    TypeNameSegment seg = targetList[targetIndex];
                                    seg.SetExpectedGenericArgCount(genericArgCount);
                                    targetList[targetIndex] = seg;
                                }
                                else
                                {
                                    currentState = ParsingState.Error;
                                    break;
                                }

                                (currentState, curPos) = DetermineNextStateAndPos(name, curPos);
                                break;
                            }
                        case ParsingState.ParsingGenericArgAssemblySpecifier:
                            {
                                // Nothing to do here, really, just skip the assembly name specified in the generic arg type
                                while (curPos < name.Length && name[curPos] != ']')
                                    curPos++;

                                (currentState, curPos) = DetermineNextStateAndPos(name, curPos);
                                break;
                            }
                        case ParsingState.ParsingGenericArgs:
                            {
                                // Start parsing the list of generic types, this just entails marking that we are parsing a generic arg list. NOTE: to support nested generic arg lsits we
                                // have to keep track of list count, not just a simple bool are/aren't parsing.
                                parsingGenericArgListDepth++;
                                currentState = ParsingState.ParsingTypeName;
                                break;
                            }
                        case ParsingState.ParsingArraySpecifier:
                            {
                                // Parse the array specifier, this mainly is to catch multi-dimensional arrays

                                // There is always at least a single dimenision in arrays, every comma counts as one more
                                int arrayDimensions = 1;

                                // Calculate the array dimensions
                                while ((curPos < name.Length) && (name[curPos] != ']'))
                                {
                                    if (name[curPos] == ',')
                                        arrayDimensions++;

                                    curPos++;
                                }

                                // Consume the final ] of the array specifier, unless we are at the end of the string already
                                if (curPos != name.Length)
                                    curPos++;

                                if (parsingGenericArgListDepth != 0 || nameSegments != null)
                                {
                                    // NOTE: TypeNameSegment is a struct to avoid heap allocations, that means we have to extract / modify / re-store to ensure the updated state gets back into whatever
                                    // list this came from.
                                    List<TypeNameSegment>? targetList = parsingGenericArgListDepth != 0 ? genericArgs : nameSegments;

                                    if (targetList != null)
                                    {
                                        int targetIndex = targetList.Count - 1;

                                        TypeNameSegment targetSegment = targetList[targetIndex];
                                        targetSegment.SetArrayDimensions(arrayDimensions);
                                        targetList[targetIndex] = targetSegment;
                                    }
                                    else
                                    {
                                        currentState = ParsingState.Error;
                                        break;
                                    }

                                    (currentState, curPos) = DetermineNextStateAndPos(name, curPos);
                                    if (genericArgs == null || genericArgs.Count == 0 && currentState == ParsingState.Done)
                                    {
                                        // Special case: Return original string in cases like this:
                                        //
                                        // System.String[,,,] or System.Int32[][]
                                        if (ReturnOriginalDACString(curPos, name.Length, nameSegments))
                                            return name;
                                    }
                                }
                                else
                                {
                                    Debug.Fail("Inside ParsingArraySpecifier but we don't think we are parsing generic params and have nothing on the top-level name segment list.");
                                    currentState = ParsingState.Error;
                                }

                                break;
                            }
                        case ParsingState.ResolveParsedGenericList:
                            {
                                // We are done with this level of arguments in terms of parsing, now we just have to apply them to the types they belong with (from previous parsing levels or the
                                // top-level).
                                parsingGenericArgListDepth--;

                                if (genericArgs == null || genericArgs.Count == 0)
                                {
                                    // For top-level types with multiple-level generic arg lists (so a type with a generic arg which itself is a generic type) as we unwind the nested generic args
                                    // lists we can end up wth no more work to do upon exiting a level (because we already propagated the info backwards before getting here), in which case, do nothing.
                                    (currentState, curPos) = DetermineNextStateAndPos(name, curPos);
                                    break;
                                }

                                (currentState, curPos) = ResolveParsedGenericList(name, curPos, parsingGenericArgListDepth, nameSegments, genericArgs);
                                break;
                            }
                    }
                }

                if (currentState == ParsingState.Error || nameSegments == null)
                {
                    // If we have encountered something we failed on, return the DAC string so at least there is SOMETHING
                    Debug.WriteLine($"Failed Parsing DAC Name: {name}");
                    return name;
                }

                //Build the final result from all the type name segments we have
                StringBuilder result = new StringBuilder();
                foreach (TypeNameSegment segment in nameSegments)
                    segment.ToString(result);

                return result.ToString();
            }
            catch (Exception e)
            {
                Debug.WriteLine($"Encountered an exception while parsing name {name}: {e}");
                return name;
            }
        }