示例#1
0
    private void AssertRuleReplacement(
        string ruleText,
        int[] sourceSymbols            = null,
        float[][] sourceParameters     = null,
        string expectedReplacementText = null,
        string axiom = null,
        int ruleParamMemoryStartIndex = 0,
        int matchIndex                      = 0,
        int paramTempMemorySize             = 0,
        float[] globalParams                = null,
        string[] globalParamNames           = null,
        int expectedReplacementPatternIndex = 0,
        string includedSymbols              = "[]ABCDE"
        )
    {
        var totalIncluded = new HashSet <int>(includedSymbols.Select(x => (int)x));

        globalParamNames = globalParamNames ?? new string[0];

        using var symbols = new DependencyTracker <SymbolString <float> >(
                  axiom == null ?
                  new SymbolString <float>(sourceSymbols, sourceParameters)
            : SymbolString <float> .FromString(axiom, Allocator.Persistent)
                  );
        using var expectedReplacement = SymbolString <float> .FromString(expectedReplacementText, Allocator.Persistent);

        var ruleFromString = new BasicRule(
            RuleParser.ParseToRule(ruleText, x => x, globalParameters: globalParamNames),
            '[', ']');

        using var ruleNativeData = new SystemLevelRuleNativeData(new[] { ruleFromString });
        var nativeWriter = new SymbolSeriesMatcherNativeDataWriter();

        ruleFromString.WriteDataIntoMemory(ruleNativeData, nativeWriter);

        globalParams           = globalParams ?? new float[0];
        using var globalNative = new NativeArray <float>(globalParams, Allocator.Persistent);

        var expectedTotalParamReplacement = expectedReplacement.parameters.data.Length;

        using var paramMemory = new NativeArray <float>(paramTempMemorySize, Allocator.Persistent);
        using var branchCache = new SymbolStringBranchingCache('[', ']', new[] { totalIncluded }, ruleNativeData);
        branchCache.BuildJumpIndexesFromSymbols(symbols);
        var random          = new Unity.Mathematics.Random();
        var matchSingleData = new LSystemSingleSymbolMatchData
        {
            isTrivial = false,
            tmpParameterMemorySpace = JaggedIndexing.GetWithNoLength(ruleParamMemoryStartIndex)
        };

        var potentialMatch = ruleFromString.AsBlittable().PreMatchCapturedParametersWithoutConditional(
            branchCache,
            symbols.Data,
            matchIndex,
            paramMemory,
            matchSingleData.tmpParameterMemorySpace.index,
            ref matchSingleData,
            new TmpNativeStack <SymbolStringBranchingCache.BranchEventData>(5),
            globalNative,
            ruleNativeData.dynamicOperatorMemory,
            ref random,
            ruleNativeData.ruleOutcomeMemorySpace
            );

        Assert.IsTrue(potentialMatch);
        Assert.AreEqual(expectedReplacementPatternIndex, matchSingleData.selectedReplacementPattern);
        Assert.AreEqual(paramTempMemorySize, matchSingleData.tmpParameterMemorySpace.length, "parameter temp memory size mismatch");
        Assert.AreEqual(expectedReplacement.symbols.Length, matchSingleData.replacementSymbolIndexing.length, "replacement symbols size mismatch");
        Assert.AreEqual(expectedReplacement.parameters.data.Length, matchSingleData.replacementParameterIndexing.length, "replacement parameter size mismatch");

        matchSingleData.replacementSymbolIndexing.index    = 0;
        matchSingleData.replacementParameterIndexing.index = 0;

        using var resultSymbols = new SymbolString <float>(
                  expectedReplacement.symbols.Length,
                  expectedReplacement.parameters.data.Length,
                  Allocator.Persistent);
        ruleFromString.AsBlittable().WriteReplacementSymbols(
            globalNative,
            paramMemory,
            resultSymbols,
            matchSingleData,
            ruleNativeData.dynamicOperatorMemory,
            ruleNativeData.replacementsSymbolMemorySpace,
            ruleNativeData.ruleOutcomeMemorySpace,
            ruleNativeData.structExpressionMemorySpace
            );

        Assert.AreEqual(expectedReplacementText, resultSymbols.ToString());
        Assert.IsTrue(expectedReplacement.Equals(resultSymbols));
    }
示例#2
0
        /* Function: ScoreScopeInterpretation
         * A function used by <ScoreInterpretation()> to determine the C and S fields of the score for the passed interpretation
         * using only the scope.  Only those fields and the trailing 1 will be set in the returned score.  If the interpretation doesn't
         * match using the scope, it will return zero.
         */
        private long ScoreScopeInterpretation(Topic topic, Link link, SymbolString interpretation)
        {
            // --C----- -------- -------- -SSSSSSS SSS----- -------- -------- -------1
            // C - Whether the topic and link's capitalization match if it matters to the language.
            // S - How high on the scope list the symbol match is.

            Language    topicLanguage = EngineInstance.Languages.FromID(topic.LanguageID);
            CommentType commentType   = EngineInstance.CommentTypes.FromID(topic.CommentTypeID);


            // Values of C:
            //		Natural Docs links:
            //			1 - Topic is documentation, case matches
            //			1 - Topic is documentation, case differs
            //			1 - Topic is file, case matches
            //			1 - Topic is file, case differs
            //			1 - Topic is code, topic language is case sensitive, case matches
            //			0 - Topic is code, topic language is case sensitive, case differs
            //			1 - Topic is code, topic language is case insensitive, case matches
            //			1 - Topic is code, topic language is case insensitive, case differs
            //		Type/Class Parent links:
            //			Assuming they're the same language...
            //			X - Topic is documentation, case matches
            //			X - Topic is documentation, case differs
            //			X - Topic is file, case matches
            //			X - Topic is file, case differs
            //			1 - Topic is code, language is case sensitive, case matches
            //			X - Topic is code, language is case sensitive, case differs
            //			1 - Topic is code, language is case insensitive, case matches
            //			1 - Topic is code, language is case insensitive, case differs

            bool caseFlagged;
            bool caseRequired;

            if (link.Type == LinkType.NaturalDocs)
            {
                caseRequired = false;
                caseFlagged  = (commentType.IsCode && topicLanguage.CaseSensitive);
            }
            else
            {
                if (commentType.IsCode == false)
                {
                    return(0);
                }

                caseRequired = topicLanguage.CaseSensitive;
                caseFlagged  = false;
            }


            // --C----- -------- -------- -SSSSSSS SSS----- -------- -------- -------1
            // Our baseline.

            long score = 0x0000000000000001;

            int scopeListIndex;


            // If we match as a global symbol...

            if (string.Compare(topic.Symbol, interpretation, !caseRequired) == 0)
            {
                if (link.Context.ScopeIsGlobal)
                {
                    scopeListIndex = 0;
                }
                else
                {
                    // Conceptually, we had to walk down the entire hierachy to get to global:
                    //    Scope A.B.C = A.B.C.Name, A.B.Name, A.Name, Name = Index 3
                    // so the scope list index is the number of dividers in the scope plus one.

                    int linkScopeIndex, linkScopeLength;
                    link.Context.GetRawTextScope(out linkScopeIndex, out linkScopeLength);

                    int dividers = link.Context.RawText.Count(SymbolString.SeparatorChar, linkScopeIndex, linkScopeLength);
                    scopeListIndex = dividers + 1;
                }

                // --C----- -------- -------- -SSSSSSS SSS----- -------- -------- -------=
                // Apply C
                if (!caseFlagged || string.Compare(topic.Symbol, interpretation, false) == 0)
                {
                    score |= 0x2000000000000000;
                }
            }


            // If the topic ends with the interepretation, such as "A.B.C.Name" and "Name"...

            else if (topic.Symbol.EndsWith(interpretation, !caseRequired))
            {
                string topicSymbolString = topic.Symbol.ToString();
                int    topicScopeIndex   = 0;
                int    topicScopeLength  = topicSymbolString.Length - interpretation.ToString().Length - 1;

                // See if the link's scope can completely encompass the remaining scope:
                //    Topic A.B.C.Name + Link Name + Link Scope A.B.C = yes
                //    Topic A.B.C.Name + Link Name + Link Scope A.B = no
                //    Topic A.B.C.Name + Link Name + Link Scope A.B.C.D = yes, it can walk up the hierarchy
                //    Topic A.B.C.Name + Link Name + Link Scope A.B.CC = no, can't split a word
                //    Topic A.B.C.Name + Link Name + Link Scope X.Y.Z = no

                string linkContextString = link.Context.RawText;
                int    linkScopeIndex, linkScopeLength;
                link.Context.GetRawTextScope(out linkScopeIndex, out linkScopeLength);

                // If the remaining topic scope is a substring or equal to the link scope...
                if (topicScopeLength <= linkScopeLength &&
                    string.Compare(linkContextString, linkScopeIndex, topicSymbolString, topicScopeIndex, topicScopeLength, !caseRequired) == 0)
                {
                    if (topicScopeLength == linkScopeLength)
                    {
                        // If it's an exact match, this is considered the first entry on our conceptual scope list.
                        scopeListIndex = 0;
                    }

                    else                     // topicScopeLength < linkScopeLength
                    {
                        // If the scope was a substring, the next character needs to be a separator so we don't split a word.
                        if (linkContextString[topicScopeLength] != SymbolString.SeparatorChar)
                        {
                            return(0);
                        }

                        // The scope list index is the number of separators we trimmed off:
                        //    Link scope: A.B.C.D
                        //    Remaining topic scope: A.B
                        //    Scope list:
                        //       0 - A.B.C.D
                        //       1 - A.B.C
                        //       2 - A.B
                        //       3 - A
                        //       4 - global
                        scopeListIndex = linkContextString.Count(SymbolString.SeparatorChar, linkScopeIndex + topicScopeLength,
                                                                 linkScopeLength - topicScopeLength);
                    }

                    // --C----- -------- -------- -SSSSSSS SSS----- -------- -------- -------=
                    // Apply C
                    if (!caseFlagged ||
                        (topicSymbolString.EndsWith(interpretation) == true &&
                         string.Compare(linkContextString, linkScopeIndex, topicSymbolString, topicScopeIndex, topicScopeLength, false) == 0))
                    {
                        score |= 0x2000000000000000;
                    }
                }
                else
                {
                    return(0);
                }
            }
            else
            {
                return(0);
            }


            // --=----- -------- -------- -SSSSSSS SSS----- -------- -------- -------=
            // Encode the scope index.  We want lower indexes to have a higher score.

            if (scopeListIndex > 1023)
            {
                scopeListIndex = 1023;
            }

            long scopeListBits = 1023 - scopeListIndex;

            scopeListBits <<= 29;

            score |= scopeListBits;

            return(score);
        }
示例#3
0
        /* Function: ScoreUsingInterpretation
         * A function used by <ScoreInterpretation()> to determine the C and S fields of the score for the passed interpretation
         * using only the using statements.  Only those fields and the trailing 1 will be set in the returned score.  If the interpretation
         * doesn't match using the using statements, it will return zero.
         */
        private long ScoreUsingInterpretation(Topic topic, Link link, SymbolString interpretation)
        {
            // --C----- -------- -------- -SSSSSSS SSS----- -------- -------- -------1
            // C - Whether the topic and link's capitalization match if it matters to the language.
            // S - How high on the scope list the symbol match is.

            IList <UsingString> usingStrings = link.Context.GetUsingStatements();

            if (usingStrings == null || usingStrings.Count == 0)
            {
                return(0);
            }

            Language    topicLanguage = EngineInstance.Languages.FromID(topic.LanguageID);
            CommentType commentType   = EngineInstance.CommentTypes.FromID(topic.CommentTypeID);


            // Values of C:
            //		Natural Docs links:
            //			1 - Topic is documentation, case matches
            //			1 - Topic is documentation, case differs
            //			1 - Topic is file, case matches
            //			1 - Topic is file, case differs
            //			1 - Topic is code, topic language is case sensitive, case matches
            //			0 - Topic is code, topic language is case sensitive, case differs
            //			1 - Topic is code, topic language is case insensitive, case matches
            //			1 - Topic is code, topic language is case insensitive, case differs
            //		Type/Class Parent links:
            //			Assuming they're the same language...
            //			X - Topic is documentation, case matches
            //			X - Topic is documentation, case differs
            //			X - Topic is file, case matches
            //			X - Topic is file, case differs
            //			1 - Topic is code, language is case sensitive, case matches
            //			X - Topic is code, language is case sensitive, case differs
            //			1 - Topic is code, language is case insensitive, case matches
            //			1 - Topic is code, language is case insensitive, case differs

            bool caseFlagged;
            bool caseRequired;

            if (link.Type == LinkType.NaturalDocs)
            {
                caseRequired = false;
                caseFlagged  = (commentType.IsCode && topicLanguage.CaseSensitive);
            }
            else
            {
                if (commentType.IsCode == false)
                {
                    return(0);
                }

                caseRequired = topicLanguage.CaseSensitive;
                caseFlagged  = false;
            }


            // Find the scope list index to start at, since the actual scopes come before the using statements.
            //    Scope list:
            //       0 - A.B.C.Link
            //       1 - A.B.Link
            //       2 - A.Link
            //       3 - Link
            //       4 - Link + first using statement
            // So if there's a scope, the starting index is the number of separators in the scope + 2.  Otherwise it's one.
            //    Scope list:
            //       0 - Link
            //       1 - Link + first using statement

            int scopeListIndex;

            if (link.Context.ScopeIsGlobal)
            {
                scopeListIndex = 1;
            }
            else
            {
                int scopeIndex, scopeLength;
                link.Context.GetRawTextScope(out scopeIndex, out scopeLength);

                scopeListIndex = link.Context.RawText.Count(SymbolString.SeparatorChar, scopeIndex, scopeLength) + 2;
            }


            // Go through each using statement looking for the best score.

            long bestScore = 0;

            foreach (var usingString in usingStrings)
            {
                SymbolString newInterpretation;
                bool         newInterpretationPossible;

                if (usingString.Type == UsingString.UsingType.AddPrefix)
                {
                    newInterpretation         = usingString.PrefixToAdd + interpretation;
                    newInterpretationPossible = true;
                }
                else if (usingString.Type == UsingString.UsingType.ReplacePrefix)
                {
                    SymbolString prefixToRemove       = usingString.PrefixToRemove;
                    string       prefixToRemoveString = prefixToRemove.ToString();
                    string       interpretationString = interpretation.ToString();

                    if (interpretationString.Length > prefixToRemoveString.Length &&
                        interpretation.StartsWith(prefixToRemove, !caseRequired))
                    {
                        newInterpretation         = usingString.PrefixToAdd + SymbolString.FromExportedString(interpretationString.Substring(prefixToRemoveString.Length + 1));
                        newInterpretationPossible = true;
                    }
                    else
                    {
                        newInterpretation         = new SymbolString();                  // to make the compiler shut up
                        newInterpretationPossible = false;
                    }
                }
                else
                {
                    throw new NotImplementedException();
                }


                if (newInterpretationPossible && string.Compare(newInterpretation, topic.Symbol, !caseRequired) == 0)
                {
                    // --C----- -------- -------- -SSSSSSS SSS----- -------- -------- -------1
                    // Our baseline.

                    long score = 0x0000000000000001;


                    // --C----- -------- -------- -SSSSSSS SSS----- -------- -------- -------=
                    // Encode the scope index.  We want lower indexes to have a higher score.

                    if (scopeListIndex > 1023)
                    {
                        scopeListIndex = 1023;
                    }

                    long scopeListBits = 1023 - scopeListIndex;
                    scopeListBits <<= 29;

                    score |= scopeListBits;


                    // --C----- -------- -------- -======= ===----- -------- -------- -------=
                    // Determine C.  If C is set we can quit early because it would be impossible for a later using statement to
                    // generate a higher score.

                    if (!caseFlagged || string.Compare(newInterpretation, topic.Symbol, false) == 0)
                    {
                        score    |= 0x2000000000000000;
                        bestScore = score;
                        break;
                    }
                    else
                    {
                        if (score > bestScore)
                        {
                            bestScore = score;
                        }
                    }
                }

                scopeListIndex++;
            }

            return(bestScore);
        }
示例#4
0
        public override string OutputOf(IList <string> commands)
        {
            StringBuilder output           = new StringBuilder();
            bool          lastWasLineBreak = true;

            Topic topic = new Topic(EngineInstance.CommentTypes);

            topic.LanguageID    = EngineInstance.Languages.FromName("C#").ID;
            topic.CommentTypeID = EngineInstance.CommentTypes.FromKeyword("Function").ID;

            Link link = new Engine.Links.Link();

            link.LanguageID = EngineInstance.Languages.FromName("C#").ID;
            link.Type       = Engine.Links.LinkType.NaturalDocs;
            bool linkIsPlainText = true;

            string show = "all";

            for (int i = 0; i < commands.Count; i++)
            {
                string command = commands[i];
                if (command.EndsWith(";"))
                {
                    command = command.Substring(0, command.Length - 1).TrimEnd();
                }

                Match  match = GetPropertyRegex.Match(command);
                string target, property, value, valueString;

                Match addPrefixMatch     = null;
                Match replacePrefixMatch = null;

                if (match.Success)
                {
                    target   = match.Groups[1].ToString().ToLower();
                    property = match.Groups[2].ToString().ToLower();
                    value    = match.Groups[3].ToString();

                    if (value.Length >= 2 && value[0] == '"' && value[value.Length - 1] == '"')
                    {
                        valueString = value.Substring(1, value.Length - 2).Trim();
                    }
                    else
                    {
                        value       = value.ToLower();
                        valueString = null;
                    }
                }
                else
                {
                    addPrefixMatch     = AddPrefixRegex.Match(command);
                    replacePrefixMatch = ReplacePrefixRegex.Match(command);

                    if (addPrefixMatch.Success || replacePrefixMatch.Success)
                    {
                        target      = "link";
                        property    = "using";
                        value       = null;
                        valueString = null;
                    }
                    else
                    {
                        target      = null;
                        property    = null;
                        value       = null;
                        valueString = null;
                    }
                }

                var lcCommand = command.ToLower();

                try
                {
                    if (command == "")
                    {
                        if (!lastWasLineBreak)
                        {
                            output.AppendLine();
                            lastWasLineBreak = true;
                        }
                    }
                    else if (command.StartsWith("//"))
                    {
                        output.AppendLine(command);
                        lastWasLineBreak = false;
                    }


                    // Topic properties

                    else if (target == "topic")
                    {
                        if (property == "languagename")
                        {
                            if (valueString == null)
                            {
                                throw new Exception("Topic.LanguageName must be set to a string value.");
                            }

                            var language = EngineInstance.Languages.FromName(valueString);

                            if (language == null)
                            {
                                throw new Exception("\"" + valueString + "\" is not recognized as a language.");
                            }

                            topic.LanguageID = language.ID;
                        }
                        else if (property == "keyword")
                        {
                            if (valueString == null)
                            {
                                throw new Exception("Topic.Keyword must be set to a string value.");
                            }

                            var commentType = EngineInstance.CommentTypes.FromKeyword(valueString);

                            if (commentType == null)
                            {
                                throw new Exception("\"" + valueString + "\" is not recognized as a keyword.");
                            }

                            topic.CommentTypeID = commentType.ID;
                        }
                        else if (property == "title")
                        {
                            if (valueString == null)
                            {
                                throw new Exception("Topic.Title must be set to a string value.");
                            }
                            if (valueString == "")
                            {
                                throw new Exception("Topic.Title cannot be set to an empty string.");
                            }

                            topic.Title = valueString;
                        }
                        else if (property == "body")
                        {
                            if (value == "null")
                            {
                                topic.Body = null;
                            }
                            else if (valueString == null)
                            {
                                throw new Exception("Topic.Body must be set to null or a string value.");
                            }
                            else
                            {
                                topic.Body = valueString;
                            }
                        }
                        else if (property == "prototype")
                        {
                            if (value == "null")
                            {
                                topic.Prototype = null;
                            }
                            else if (valueString == null)
                            {
                                throw new Exception("Topic.Prototype must be set to null or a string value.");
                            }
                            else
                            {
                                topic.Prototype = valueString;
                            }
                        }
                        else if (property == "scope")
                        {
                            if (value == "null")
                            {
                                ContextString temp = topic.PrototypeContext;
                                temp.Scope             = new SymbolString();
                                topic.PrototypeContext = temp;
                            }
                            else if (valueString == null)
                            {
                                throw new Exception("Topic.Scope must be set to null or a string value.");
                            }
                            else
                            {
                                ContextString temp = topic.PrototypeContext;
                                temp.Scope             = SymbolString.FromPlainText_NoParameters(valueString);
                                topic.PrototypeContext = temp;
                            }
                        }
                        else
                        {
                            throw new Exception("\"" + property + "\" is not a recognized Topic property.");
                        }

                        // Leave lastWasLineBreak alone since we're not generating output.
                    }


                    // Link properties

                    else if (target == "link")
                    {
                        if (property == "languagename")
                        {
                            if (valueString == null)
                            {
                                throw new Exception("Link.LanguageName must be set to a string value.");
                            }

                            var language = EngineInstance.Languages.FromName(valueString);

                            if (language == null)
                            {
                                throw new Exception("\"" + valueString + "\" is not recognized as a language.");
                            }

                            link.LanguageID = language.ID;
                        }
                        else if (property == "type")
                        {
                            if (valueString == null)
                            {
                                throw new Exception("Link.Type must be set to a string value.");
                            }

                            string lcValueString = valueString.ToLower();

                            if (lcValueString == "naturaldocs" || lcValueString == "natural docs")
                            {
                                link.Type = LinkType.NaturalDocs;
                            }
                            else if (lcValueString == "type")
                            {
                                link.Type = LinkType.Type;
                            }
                            else if (lcValueString == "classparent" || lcValueString == "class parent")
                            {
                                link.Type = LinkType.ClassParent;
                            }
                            else
                            {
                                throw new Exception("\"" + valueString + "\" is not recognized as a link type.");
                            }
                        }
                        else if (property == "text")
                        {
                            if (valueString == null)
                            {
                                throw new Exception("Link.Text must be set to a string value");
                            }
                            if (valueString == "")
                            {
                                throw new Exception("Link.Text cannot be set to an empty string.");
                            }

                            link.TextOrSymbol = valueString;
                            linkIsPlainText   = true;
                        }
                        else if (property == "scope")
                        {
                            if (value == "null")
                            {
                                ContextString temp = link.Context;
                                temp.Scope   = new SymbolString();
                                link.Context = temp;
                            }
                            else if (valueString == null)
                            {
                                throw new Exception("Link.Scope must be set to null or a string value.");
                            }
                            else if (valueString == "")
                            {
                                throw new Exception("Link.Scope cannot be set to an empty string.");
                            }
                            else
                            {
                                ContextString temp = link.Context;
                                temp.Scope   = SymbolString.FromPlainText_NoParameters(valueString);
                                link.Context = temp;
                            }
                        }
                        else if (property == "using")
                        {
                            if (value == "null")
                            {
                                ContextString temp = link.Context;
                                temp.ClearUsingStatements();
                                link.Context = temp;
                            }
                            else if (addPrefixMatch != null && addPrefixMatch.Success)
                            {
                                string op  = addPrefixMatch.Groups[1].ToString();
                                string add = addPrefixMatch.Groups[2].ToString();

                                ContextString temp = link.Context;

                                if (op == "=")
                                {
                                    temp.ClearUsingStatements();
                                }

                                temp.AddUsingStatement(
                                    UsingString.FromParameters(
                                        UsingString.UsingType.AddPrefix,
                                        SymbolString.FromPlainText_NoParameters(add)
                                        )
                                    );

                                link.Context = temp;
                            }
                            else if (replacePrefixMatch != null && replacePrefixMatch.Success)
                            {
                                string op     = replacePrefixMatch.Groups[1].ToString();
                                string remove = replacePrefixMatch.Groups[2].ToString();
                                string add    = replacePrefixMatch.Groups[3].ToString();

                                ContextString temp = link.Context;

                                if (op == "=")
                                {
                                    temp.ClearUsingStatements();
                                }

                                temp.AddUsingStatement(
                                    UsingString.FromParameters(
                                        UsingString.UsingType.ReplacePrefix,
                                        SymbolString.FromPlainText_NoParameters(add),
                                        SymbolString.FromPlainText_NoParameters(remove)
                                        )
                                    );

                                link.Context = temp;
                            }
                            else
                            {
                                throw new Exception("\"" + command + "\" is not a recognized Link.Using statement.");
                            }
                        }
                        else
                        {
                            throw new Exception("\"" + property + "\" is not recognized as a link property.");
                        }
                        // Leave lastWasLineBreak alone since we're not generating output.
                    }


                    // Show

                    else if (lcCommand.StartsWith("show"))
                    {
                        show = lcCommand.Substring(4);
                    }


                    // Score

                    else if (lcCommand == "score")
                    {
                        // Validate fields

                        if (topic.Title == null)
                        {
                            throw new Exception("You didn't set Topic.Title.");
                        }
                        if (link.TextOrSymbol == null)
                        {
                            throw new Exception("You didn't set Link.Text.");
                        }


                        // Calculate fields

                        string       parametersString;
                        SymbolString topicSymbol = SymbolString.FromPlainText(topic.Title, out parametersString);

                        var commentType = EngineInstance.CommentTypes.FromID(topic.CommentTypeID);

                        if (commentType.Scope == Engine.CommentTypes.CommentType.ScopeValue.Normal &&
                            topic.PrototypeContext.ScopeIsGlobal == false)
                        {
                            topicSymbol = topic.PrototypeContext.Scope + topicSymbol;
                        }

                        topic.Symbol = topicSymbol;

                        if (link.Type == LinkType.Type || link.Type == LinkType.ClassParent)
                        {
                            if (linkIsPlainText)
                            {
                                SymbolString linkSymbol = SymbolString.FromPlainText_NoParameters(link.TextOrSymbol);
                                link.TextOrSymbol = linkSymbol.ToString();
                                link.EndingSymbol = linkSymbol.EndingSymbol;
                                linkIsPlainText   = false;
                            }
                        }
                        else
                        {
                            string       ignore;
                            SymbolString linkSymbol = SymbolString.FromPlainText(link.TextOrSymbol, out ignore);
                            link.EndingSymbol = linkSymbol.EndingSymbol;
                        }


                        // Show topic

                        if (!lastWasLineBreak)
                        {
                            output.AppendLine();
                        }

                        var topicLanguage = EngineInstance.Languages.FromID(topic.LanguageID);
                        commentType = EngineInstance.CommentTypes.FromID(topic.CommentTypeID);

                        output.AppendLine(topicLanguage.Name + " " + commentType.Name + " Topic: " + topic.Title);
                        output.AppendLine("   Symbol: " + topic.Symbol.FormatWithSeparator('.'));

                        if (topic.TitleParameters != null)
                        {
                            output.AppendLine("   Title Parameters: " + topic.TitleParameters.ToString().Replace(Engine.Symbols.ParameterString.SeparatorChar, ','));
                        }
                        if (topic.PrototypeParameters != null)
                        {
                            output.AppendLine("   Prototype Parameters: " + topic.PrototypeParameters.ToString().Replace(Engine.Symbols.ParameterString.SeparatorChar, ','));
                        }
                        if (topic.Prototype != null)
                        {
                            output.AppendLine("   Prototype: " + topic.Prototype);
                        }
                        if (topic.Body != null)
                        {
                            output.AppendLine("   Body: " + topic.Body);
                        }

                        output.AppendLine();


                        // Show link

                        var linkLanguage = EngineInstance.Languages.FromID(link.LanguageID);

                        output.AppendLine(linkLanguage.Name + " " + link.Type + " Link: " + link.TextOrSymbol.Replace(Engine.Symbols.SymbolString.SeparatorChar, '*'));

                        if (link.Context.ScopeIsGlobal)
                        {
                            output.AppendLine("   Scope: Global");
                        }
                        else
                        {
                            output.AppendLine("   Scope: " + link.Context.Scope.FormatWithSeparator('.'));
                        }

                        var usingStatements = link.Context.GetUsingStatements();

                        if (usingStatements != null)
                        {
                            foreach (var usingStatement in usingStatements)
                            {
                                if (usingStatement.Type == UsingString.UsingType.AddPrefix)
                                {
                                    output.AppendLine("   Using: Add Prefix " + usingStatement.PrefixToAdd.FormatWithSeparator('.'));
                                }
                                else if (usingStatement.Type == UsingString.UsingType.ReplacePrefix)
                                {
                                    output.AppendLine("   Using: Replace Prefix " + usingStatement.PrefixToRemove.FormatWithSeparator('.') +
                                                      " with " + usingStatement.PrefixToAdd.FormatWithSeparator('.'));
                                }
                                else
                                {
                                    throw new NotImplementedException("Unexpected using type " + usingStatement.Type);
                                }
                            }
                        }

                        output.AppendLine();


                        // Show score

                        List <Engine.Links.LinkInterpretation> interpretations = null;

                        if (link.Type == LinkType.NaturalDocs)
                        {
                            string ignore;
                            interpretations = EngineInstance.Comments.NaturalDocsParser.LinkInterpretations(link.TextOrSymbol,
                                                                                                            Engine.Comments.Parsers.NaturalDocs.LinkInterpretationFlags.AllowNamedLinks |
                                                                                                            Engine.Comments.Parsers.NaturalDocs.LinkInterpretationFlags.AllowPluralsAndPossessives,
                                                                                                            out ignore);
                        }

                        long score = EngineInstance.CodeDB.ScoreLink(link, topic, 0, interpretations);

                        if (score <= 0)
                        {
                            output.AppendLine("☓☓☓ No Match ☓☓☓");
                        }
                        else
                        {
                            output.Append("Match score:");

                            if (show.Contains("all") || show.Contains("rawscore"))
                            {
                                string scoreString = score.ToString("X16");
                                output.Append(" " + scoreString.Substring(0, 4) + " " + scoreString.Substring(4, 4) +
                                              " " + scoreString.Substring(8, 4) + " " + scoreString.Substring(12, 4));
                            }

                            output.AppendLine();

                            // DEPENDENCY: This code depends on the format generated by Engine.CodeDB.ScoreLink().

                            // Format:
                            // 0LCETPPP PPPPPPPP PPPPPPPP PSSSSSSS SSSIIIII IBFFFFFF Rbbbbbbb brrrrrr1

                            // L - Whether the topic matches the link's language.
                            // C - Whether the topic and link's capitalization match if it matters to the language.
                            // E - Whether the text is an exact match with no plural or possessive conversions applied.
                            // T - Whether the link parameters exactly match the topic title parameters.
                            // P - How well the parameters match.
                            // S - How high on the scope list the symbol match is.
                            // I - How high on the interpretation list (named/plural/possessive) the match is.
                            // B - Whether the topic has a body
                            // F - How high on the list of topics that define the same symbol in the same file this is.
                            // R - Whether the topic has a prototype.
                            // b - The length of the body divided by 16.
                            // r - The length of the prototype divided by 16.

                            long LValue = (score & 0x4000000000000000) >> 62;
                            long CValue = (score & 0x2000000000000000) >> 61;
                            long EValue = (score & 0x1000000000000000) >> 60;
                            long TValue = (score & 0x0800000000000000) >> 59;
                            long PValue = (score & 0x07FFFF8000000000) >> 39;
                            long SValue = (score & 0x0000007FE0000000) >> 29;
                            long IValue = (score & 0x000000001F800000) >> 23;
                            long BValue = (score & 0x0000000000400000) >> 22;
                            long FValue = (score & 0x00000000003F0000) >> 16;
                            long RValue = (score & 0x0000000000008000) >> 15;
                            long bValue = (score & 0x0000000000007F80) >> 7;
                            long rValue = (score & 0x000000000000007E) >> 1;

                            if (show.Contains("all") || show.Contains("language"))
                            {
                                output.AppendLine("   " + (LValue == 1 ? "☒" : "☐") + " - Language");
                            }
                            if (show.Contains("all") || show.Contains("capitalization"))
                            {
                                output.AppendLine("   " + (CValue == 1 ? "☒" : "☐") + " - Capitalization");
                            }
                            if (show.Contains("all") || show.Contains("interpretation"))
                            {
                                output.AppendLine("   " + (EValue == 1 ? "☒" : "☐") + " - Exact text");
                            }

                            if (show.Contains("all") || show.Contains("parameters"))
                            {
                                output.AppendLine("   " + (TValue == 1 ? "☒" : "☐") + " - Topic title parameters");

                                output.Append("   ");
                                for (int shift = 18; shift >= 0; shift -= 2)
                                {
                                    long individualPValue = PValue >> shift;
                                    individualPValue &= 0x0000000000000003;

                                    switch (individualPValue)
                                    {
                                    case 3:
                                        output.Append("☒");
                                        break;

                                    case 2:
                                        output.Append("↑");
                                        break;

                                    case 1:
                                        output.Append("↓");
                                        break;

                                    case 0:
                                        output.Append("☐");
                                        break;
                                    }
                                }
                                output.AppendLine(" - Parameters");
                            }

                            if (show.Contains("all") || show.Contains("scope"))
                            {
                                output.AppendLine("   " + (1023 - SValue) + " - Scope index");
                                output.AppendLine("      (" + SValue + " score)");
                            }
                            if (show.Contains("all") || show.Contains("interpretation"))
                            {
                                output.AppendLine("   " + (63 - IValue) + " - Interpretation index");
                                output.AppendLine("      (" + IValue + " score)");
                            }

                            if (show.Contains("all") || show.Contains("body"))
                            {
                                output.AppendLine("   " + (BValue == 1 ? "☒" : "☐") + " - Body");
                                if (BValue == 1)
                                {
                                    output.Append("      (" + bValue + " score, " + (bValue * 16));

                                    if (bValue == 0xFF)
                                    {
                                        output.Append('+');
                                    }
                                    else
                                    {
                                        output.Append("-" + ((bValue * 16) + 15));
                                    }

                                    output.AppendLine(" characters)");
                                }
                            }

                            if (show.Contains("all") || show.Contains("prototype"))
                            {
                                output.AppendLine("   " + (RValue == 1 ? "☒" : "☐") + " - Prototype");
                                if (RValue == 1)
                                {
                                    output.Append("      (" + rValue + " score, " + (rValue * 16));

                                    if (rValue == 0x3F)
                                    {
                                        output.Append('+');
                                    }
                                    else
                                    {
                                        output.Append("-" + ((rValue * 16) + 15));
                                    }

                                    output.AppendLine(" characters)");
                                }
                            }

                            if (show.Contains("all") || show.Contains("samesymbol"))
                            {
                                output.AppendLine("   " + (63 - FValue) + " - Same symbol in same file index");
                                output.AppendLine("      (" + FValue + " score)");
                            }
                        }

                        output.AppendLine();
                        lastWasLineBreak = true;
                    }

                    else
                    {
                        throw new Exception("Unknown command " + command);
                    }
                }
                catch (Exception e)
                {
                    output.AppendLine("Command: " + command);
                    output.AppendLine("Exception: " + e.Message);
                    output.AppendLine("(" + e.GetType().ToString() + ")");
                    lastWasLineBreak = false;
                }
            }

            return(output.ToString());
        }