Пример #1
0
        /* Function: AddCommentType
         * Adds a comment type to the file.
         */
        public void AddCommentType(TextFileCommentType commentType)
        {
            if (commentTypes == null)
            {
                commentTypes = new List <TextFileCommentType>();
            }

            commentTypes.Add(commentType);
        }
Пример #2
0
        /* Function: MergeCommentTypeInto
         * Merges the settings of a <ConfigFiles.TextFileCommentType> into another one, overriding the settings of the first.  This
         * does NOT cover keywords.  The base object will be altered.
         */
        protected void MergeCommentTypeInto(ref ConfigFiles.TextFileCommentType baseCommentType,
                                            ConfigFiles.TextFileCommentType overridingCommentType)
        {
            if (overridingCommentType.HasDisplayName)
            {
                baseCommentType.SetDisplayName(overridingCommentType.DisplayName,
                                               overridingCommentType.DisplayNamePropertyLocation);
                baseCommentType.SetDisplayNameFromLocale(null, default);
            }
            if (overridingCommentType.HasDisplayNameFromLocale)
            {
                baseCommentType.SetDisplayNameFromLocale(overridingCommentType.DisplayNameFromLocale,
                                                         overridingCommentType.DisplayNameFromLocalePropertyLocation);
                baseCommentType.SetDisplayName(null, default);
            }
            if (overridingCommentType.HasPluralDisplayName)
            {
                baseCommentType.SetPluralDisplayName(overridingCommentType.PluralDisplayName,
                                                     overridingCommentType.PluralDisplayNamePropertyLocation);
                baseCommentType.SetPluralDisplayNameFromLocale(null, default);
            }
            if (overridingCommentType.HasPluralDisplayNameFromLocale)
            {
                baseCommentType.SetPluralDisplayNameFromLocale(overridingCommentType.PluralDisplayNameFromLocale,
                                                               overridingCommentType.PluralDisplayNameFromLocalePropertyLocation);
                baseCommentType.SetPluralDisplayName(null, default);
            }

            if (overridingCommentType.HasSimpleIdentifier)
            {
                baseCommentType.SetSimpleIdentifier(overridingCommentType.SimpleIdentifier,
                                                    overridingCommentType.SimpleIdentifierPropertyLocation);
            }
            if (overridingCommentType.HasScope)
            {
                baseCommentType.SetScope(overridingCommentType.Scope,
                                         overridingCommentType.ScopePropertyLocation);
            }

            // Ignore keywods

            if (overridingCommentType.HasFlags)
            {
                baseCommentType.SetFlags(overridingCommentType.Flags,
                                         overridingCommentType.FlagsPropertyLocation);
            }
        }
Пример #3
0
        /* Function: Duplicate
         * Creates an independent copy of the object and all its attributes.
         */
        public TextFileCommentType Duplicate()
        {
            TextFileCommentType copy = new TextFileCommentType(name, namePropertyLocation, alterType);

            copy.name = name;
            copy.namePropertyLocation = namePropertyLocation;
            copy.alterType            = alterType;

            copy.displayName = displayName;
            copy.displayNamePropertyLocation           = displayNamePropertyLocation;
            copy.displayNameFromLocale                 = displayNameFromLocale;
            copy.displayNameFromLocalePropertyLocation = displayNameFromLocalePropertyLocation;
            copy.pluralDisplayName = pluralDisplayName;
            copy.pluralDisplayNamePropertyLocation           = pluralDisplayNamePropertyLocation;
            copy.pluralDisplayNameFromLocale                 = pluralDisplayNameFromLocale;
            copy.pluralDisplayNameFromLocalePropertyLocation = pluralDisplayNameFromLocalePropertyLocation;

            copy.simpleIdentifier = simpleIdentifier;
            copy.simpleIdentifierPropertyLocation = simpleIdentifierPropertyLocation;
            copy.scope = scope;
            copy.scopePropertyLocation = scopePropertyLocation;

            if (keywordGroups != null)
            {
                copy.keywordGroups = new List <TextFileKeywordGroup>(keywordGroups.Count);
                foreach (var keywordGroup in keywordGroups)
                {
                    copy.keywordGroups.Add(keywordGroup.Duplicate());
                }
            }

            copy.flags = flags;
            copy.flagsPropertyLocation = flagsPropertyLocation;

            return(copy);
        }
Пример #4
0
        /* Function: FinalizeCommentType
         * Converts a <ConfigFiles.TextFileCommentType> into a <CommentType>.  This does NOT cover keywords.  Also,
         * <CommentType.SimpleIdentifier> may still be null if one wasn't defined and it cannot be generated automatically.
         */
        protected CommentType FinalizeCommentType(ConfigFiles.TextFileCommentType textCommentType)
        {
            CommentType final = new CommentType(textCommentType.Name);

            if (textCommentType.HasDisplayNameFromLocale)
            {
                final.DisplayName = Locale.Get("NaturalDocs.Engine", textCommentType.DisplayNameFromLocale);
            }
            else if (textCommentType.HasDisplayName)
            {
                final.DisplayName = textCommentType.DisplayName;
            }
            else
            {
                final.DisplayName = textCommentType.Name;
            }

            if (textCommentType.HasPluralDisplayNameFromLocale)
            {
                final.PluralDisplayName = Locale.Get("NaturalDocs.Engine", textCommentType.PluralDisplayNameFromLocale);
            }
            else if (textCommentType.HasPluralDisplayName)
            {
                final.PluralDisplayName = textCommentType.PluralDisplayName;
            }
            else
            {
                final.PluralDisplayName = final.DisplayName;
            }

            if (textCommentType.HasSimpleIdentifier)
            {
                final.SimpleIdentifier = textCommentType.SimpleIdentifier;
            }
            else
            {
                // This may end up as an empty string if there's no A-Z characters, such as if the name is in Japanese.  In this case
                // we want it to be "CommentTypeID[number]" but the number isn't determind yet, so leave it as null for now.
                string simpleIdentifier = final.Name.OnlyAToZ();

                if (!string.IsNullOrEmpty(simpleIdentifier))
                {
                    final.SimpleIdentifier = simpleIdentifier;
                }
            }

            if (textCommentType.HasScope)
            {
                final.Scope = (CommentType.ScopeValue)textCommentType.Scope;
            }
            else
            {
                final.Scope = CommentType.ScopeValue.Normal;
            }

            if (textCommentType.HasFlags)
            {
                final.Flags = AddImpliedFlags((CommentType.FlagValue)textCommentType.Flags);
            }
            else
            {
                final.Flags = CommentType.FlagValue.Code;
            }

            return(final);
        }
Пример #5
0
        /* Function: ValidateCommentType
         * Validates all the settings in a <ConfigFiles.TextFileCommentType>.  Returns whether it is valid, and adds any errors it finds
         * to errorList.
         */
        protected bool ValidateCommentType(ConfigFiles.TextFileCommentType commentType, Errors.ErrorList errorList)
        {
            int startingErrorCount = errorList.Count;


            // Validate names

            if (commentType.DisplayNamePropertyLocation.IsDefined &&
                commentType.DisplayNameFromLocalePropertyLocation.IsDefined)
            {
                // Put them in the proper order so the error appears on the second one
                string           first, second;
                PropertyLocation secondPropertyLocation;

                if (commentType.DisplayNamePropertyLocation.LineNumber <
                    commentType.DisplayNameFromLocalePropertyLocation.LineNumber)
                {
                    first  = "Display Name";
                    second = "Display Name From Locale";
                    secondPropertyLocation = commentType.DisplayNameFromLocalePropertyLocation;
                }
                else
                {
                    first  = "Display Name From Locale";
                    second = "Display Name";
                    secondPropertyLocation = commentType.DisplayNamePropertyLocation;
                }

                errorList.Add(Locale.Get("NaturalDocs.Engine", "Comments.txt.CannotDefineXWhenYIsDefined(x,y)", second, first),
                              secondPropertyLocation);
            }

            if (commentType.PluralDisplayNamePropertyLocation.IsDefined &&
                commentType.PluralDisplayNameFromLocalePropertyLocation.IsDefined)
            {
                string           first, second;
                PropertyLocation secondPropertyLocation;

                if (commentType.PluralDisplayNamePropertyLocation.LineNumber <
                    commentType.PluralDisplayNameFromLocalePropertyLocation.LineNumber)
                {
                    first  = "Plural Display Name";
                    second = "Plural Display Name From Locale";
                    secondPropertyLocation = commentType.PluralDisplayNameFromLocalePropertyLocation;
                }
                else
                {
                    first  = "Plural Display Name From Locale";
                    second = "Plural Display Name";
                    secondPropertyLocation = commentType.PluralDisplayNamePropertyLocation;
                }

                errorList.Add(Locale.Get("NaturalDocs.Engine", "Comments.txt.CannotDefineXWhenYIsDefined(x,y)", second, first),
                              secondPropertyLocation);
            }


            // Validate flags

            if (commentType.Flags != null)
            {
                var flags = (CommentType.FlagValue)commentType.Flags;

                bool codeFlag              = ((flags & CommentType.FlagValue.Code) != 0);
                bool fileFlag              = ((flags & CommentType.FlagValue.File) != 0);
                bool documentationFlag     = ((flags & CommentType.FlagValue.Documentation) != 0);
                bool variableTypeFlag      = ((flags & CommentType.FlagValue.VariableType) != 0);
                bool classHierarchyFlag    = ((flags & CommentType.FlagValue.ClassHierarchy) != 0);
                bool databaseHierarchyFlag = ((flags & CommentType.FlagValue.DatabaseHierarchy) != 0);
                bool enumFlag              = ((flags & CommentType.FlagValue.Enum) != 0);


                // Check that only one of Code, File, and Documentation are defined

                if (codeFlag)
                {
                    if (fileFlag && documentationFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b,c)", "Code", "File", "Documentation"),
                                      commentType.FlagsPropertyLocation);
                    }
                    else if (fileFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "Code", "File"),
                                      commentType.FlagsPropertyLocation);
                    }
                    else if (documentationFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "Code", "Documentation"),
                                      commentType.FlagsPropertyLocation);
                    }
                }
                else if (fileFlag)
                {
                    if (documentationFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "File", "Documentation"),
                                      commentType.FlagsPropertyLocation);
                    }
                }


                // Check that File and Documentation aren't used with any of the other flags

                if (fileFlag)
                {
                    if (variableTypeFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "File", "Variable Type"),
                                      commentType.FlagsPropertyLocation);
                    }
                    if (classHierarchyFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "File", "Class Hierarchy"),
                                      commentType.FlagsPropertyLocation);
                    }
                    if (databaseHierarchyFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "File", "Database Hierarchy"),
                                      commentType.FlagsPropertyLocation);
                    }
                    if (enumFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "File", "Enum"),
                                      commentType.FlagsPropertyLocation);
                    }
                }

                if (documentationFlag)
                {
                    if (variableTypeFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "Documentation", "Variable Type"),
                                      commentType.FlagsPropertyLocation);
                    }
                    if (classHierarchyFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "Documentation", "Class Hierarchy"),
                                      commentType.FlagsPropertyLocation);
                    }
                    if (databaseHierarchyFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "Documentation", "Database Hierarchy"),
                                      commentType.FlagsPropertyLocation);
                    }
                    if (enumFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "Documentation", "Enum"),
                                      commentType.FlagsPropertyLocation);
                    }
                }


                // Check that Class Hierarchy and Database Hierarchy aren't both defined

                if (classHierarchyFlag && databaseHierarchyFlag)
                {
                    errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.CantCombine(a,b)", "Class Hierarchy", "Database Hierarchy"),
                                  commentType.FlagsPropertyLocation);
                }


                // Check that Class Hierarchy and Database Hierarchy comment types also have Scope: Start

                if (commentType.Scope != CommentType.ScopeValue.Start)
                {
                    // This is an error if Scope isn't defined because it's too big a behavior change to just make it implied.

                    if (classHierarchyFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.FlagRequiresScope(flag,scope)", "Class Hierarchy", "Start"),
                                      commentType.FlagsPropertyLocation);
                    }
                    if (databaseHierarchyFlag)
                    {
                        errorList.Add(Locale.Get("NaturalDocs.Engine", "CommentTypeFlags.FlagRequiresScope(flag,scope)", "Database Hierarchy", "Start"),
                                      commentType.FlagsPropertyLocation);
                    }
                }

                // Variable Type and Enum are okay.  We already know they aren't being used with File or Documentation, and they're safe
                // to use with Class Hierarchy and Database Hierarchy.
            }

            return(errorList.Count == startingErrorCount);
        }
Пример #6
0
        // Group: Loading Functions
        // __________________________________________________________________________


        /* Function: Load
         *
         * Loads the contents of a <Comments.txt> file into a <ConfigFiles.TextFile>, returning whether it was successful.  If it
         * was unsuccessful config will be null and it will place errors on the errorList.
         *
         * Parameters:
         *
         *		filename - The <Path> where the file is located.
         *		propertySource - The <Engine.Config.PropertySource> associated with the file.
         *		errorList - If it couldn't successfully parse the file it will add error messages to this list.
         *		config - The contents of the file as a <ConfigFiles.TextFile>.
         */
        public bool Load(Path filename, Engine.Config.PropertySource propertySource, Errors.ErrorList errorList,
                         out ConfigFiles.TextFile config)
        {
            int previousErrorCount = errorList.Count;

            using (ConfigFile file = new ConfigFile())
            {
                bool openResult = file.Open(filename,
                                            propertySource,
                                            ConfigFile.FileFormatFlags.CondenseIdentifierWhitespace |
                                            ConfigFile.FileFormatFlags.CondenseValueWhitespace |
                                            ConfigFile.FileFormatFlags.SupportsNullValueLines |
                                            ConfigFile.FileFormatFlags.SupportsRawValueLines |
                                            ConfigFile.FileFormatFlags.MakeIdentifiersLowercase,
                                            errorList);

                if (openResult == false)
                {
                    config = null;
                    return(false);
                }

                config = new ConfigFiles.TextFile();

                TextFileCommentType  currentCommentType  = null;
                TextFileKeywordGroup currentKeywordGroup = null;
                bool inKeywords = false;
                bool inTags     = false;

                while (file.Get(out string identifier, out string value))
                {
                    //
                    // Identifierless lines
                    //

                    if (identifier == null)
                    {
                        // Keywords

                        if (inKeywords)
                        {
                            // Separate keywords

                            string keyword, pluralKeyword;
                            int    commaIndex = value.IndexOf(',');

                            if (commaIndex == -1)
                            {
                                keyword       = value;
                                pluralKeyword = null;
                            }
                            else
                            {
                                keyword       = value.Substring(0, commaIndex).TrimEnd();
                                pluralKeyword = value.Substring(commaIndex + 1).TrimStart();

                                if (pluralKeyword.IndexOf(',') != -1)
                                {
                                    file.AddError(
                                        Locale.Get("NaturalDocs.Engine", "Comments.txt.NoMoreThanTwoKeywordsOnALine")
                                        );
                                }
                            }


                            // Check for banned characters

                            int  bannedCharIndex = keyword.IndexOfAny(BannedKeywordChars);
                            char bannedChar      = '\0';

                            if (bannedCharIndex != -1)
                            {
                                bannedChar = keyword[bannedCharIndex];
                            }
                            else if (pluralKeyword != null)
                            {
                                bannedCharIndex = pluralKeyword.IndexOfAny(BannedKeywordChars);

                                if (bannedCharIndex != -1)
                                {
                                    bannedChar = pluralKeyword[bannedCharIndex];
                                }
                            }

                            if (bannedChar != '\0')
                            {
                                file.AddError(
                                    Locale.Get("NaturalDocs.Engine", "Comments.txt.KeywordsCannotContain(char)", bannedChar)
                                    );
                                // Continue parsing
                            }


                            // Add to config

                            currentKeywordGroup.Add(keyword, pluralKeyword);
                        }


                        // Tags, only a single value allowed

                        else if (inTags)
                        {
                            if (value.IndexOf(',') != -1)
                            {
                                file.AddError(
                                    Locale.Get("NaturalDocs.Engine", "Comments.txt.NoMoreThanOneTagOnALine")
                                    );
                            }

                            int bannedChar = value.IndexOfAny(BannedKeywordChars);
                            if (bannedChar != -1)
                            {
                                file.AddError(
                                    Locale.Get("NaturalDocs.Engine", "Comments.txt.TagsCannotContain(char)", value[bannedChar])
                                    );
                                // Continue parsing
                            }

                            config.AddTag(value, file.PropertyLocation);
                        }


                        // Raw line

                        else
                        {
                            file.AddError(
                                Locale.Get("NaturalDocs.Engine", "ConfigFile.LineNotInIdentifierValueFormat")
                                );
                        }

                        // Continue so we don't need to put all the identifier handling code in an else.
                        continue;
                    }


                    // If we're here the line has an identifier
                    currentKeywordGroup = null;
                    inKeywords          = false;
                    inTags = false;


                    //
                    // Ignore Keywords
                    //

                    if (ignoreKeywordsRegex.IsMatch(identifier))
                    {
                        currentCommentType = null;

                        currentKeywordGroup = new TextFileKeywordGroup(file.PropertyLocation);
                        config.AddIgnoredKeywordGroup(currentKeywordGroup);
                        inKeywords = true;

                        if (!string.IsNullOrEmpty(value))
                        {
                            string[] ignoredKeywordsArray = commaSeparatorRegex.Split(value);

                            foreach (string ignoredKeyword in ignoredKeywordsArray)
                            {
                                int bannedChar = ignoredKeyword.IndexOfAny(BannedKeywordChars);
                                if (bannedChar != -1)
                                {
                                    file.AddError(
                                        Locale.Get("NaturalDocs.Engine", "Comments.txt.KeywordsCannotContain(char)", ignoredKeyword[bannedChar])
                                        );
                                    // Continue parsing
                                }

                                currentKeywordGroup.Add(ignoredKeyword);
                            }
                        }
                    }


                    //
                    // Tags
                    //

                    else if (tagsRegex.IsMatch(identifier))
                    {
                        currentCommentType = null;
                        inTags             = true;

                        if (!string.IsNullOrEmpty(value))
                        {
                            string[] tagsArray = commaSeparatorRegex.Split(value);

                            foreach (string tag in tagsArray)
                            {
                                int bannedChar = tag.IndexOfAny(BannedKeywordChars);
                                if (bannedChar != -1)
                                {
                                    file.AddError(
                                        Locale.Get("NaturalDocs.Engine", "Comments.txt.TagsCannotContain(char)", tag[bannedChar])
                                        );
                                    // Continue parsing
                                }

                                config.AddTag(tag, file.PropertyLocation);
                            }
                        }
                    }


                    //
                    // Comment Type
                    //

                    else if (commentTypeRegex.IsMatch(identifier))
                    {
                        var existingCommentType = config.FindCommentType(value);

                        if (existingCommentType != null)
                        {
                            file.AddError(
                                Locale.Get("NaturalDocs.Engine", "Comments.txt.CommentTypeAlreadyExists(name)", value)
                                );

                            // Continue parsing.  We'll throw this into the existing type even though it shouldn't be overwriting
                            // its values because we want to find any other errors there are in the file.
                            currentCommentType = existingCommentType;
                        }

                        else
                        {
                            // There is no 1.6, but this covers all the 2.0 prereleases.
                            if (file.Version < "1.6" && String.Compare(value, "generic", true) == 0)
                            {
                                value = "Information";
                            }

                            currentCommentType = new TextFileCommentType(value, file.PropertyLocation);
                            config.AddCommentType(currentCommentType);
                        }
                    }


                    //
                    // Alter Comment Type
                    //

                    else if (alterCommentTypeRegex.IsMatch(identifier))
                    {
                        // We don't check if the name exists because it may exist in a different file.  We also don't check if it exists
                        // in the current file because using Alter is valid (if unnecessary) in that case and we don't want to combine
                        // their definitions.  Why?  Consider this:
                        //
                        // Comment Type: Comment Type A
                        //    Keyword: Keyword A
                        //
                        // Comment Type: Comment Type B
                        //    Keyword: Keyword B
                        //
                        // Alter Comment Type: Comment Type A
                        //    Keyword: Keyword B
                        //
                        // Keyword B should be part of Comment Type A.  However, if we merged the definitions it would appear
                        // first and be overridden by Comment Type B.  So we just create two comment type entries for A instead.

                        currentCommentType = new TextFileCommentType(value, file.PropertyLocation, alterType: true);
                        config.AddCommentType(currentCommentType);
                    }


                    //
                    // (Plural) Display Name (From Locale)
                    //

                    else if (displayNameRegex.IsMatch(identifier))
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else if (currentCommentType.HasDisplayNameFromLocale)
                        {
                            file.AddError(
                                Locale.Get("NaturalDocs.Engine", "Comments.txt.CannotDefineXWhenYIsDefined(x,y)", "Display Name", "Display Name from Locale")
                                );
                        }
                        else
                        {
                            currentCommentType.SetDisplayName(value, file.PropertyLocation);
                        }
                    }
                    else if (pluralDisplayNameRegex.IsMatch(identifier))
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else if (currentCommentType.HasPluralDisplayNameFromLocale)
                        {
                            file.AddError(
                                Locale.Get("NaturalDocs.Engine", "Comments.txt.CannotDefineXWhenYIsDefined(x,y)", "Plural Display Name", "Plural Display Name from Locale")
                                );
                        }
                        else
                        {
                            currentCommentType.SetPluralDisplayName(value, file.PropertyLocation);
                        }
                    }
                    else if (displayNameFromLocaleRegex.IsMatch(identifier))
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else if (currentCommentType.HasDisplayName)
                        {
                            file.AddError(
                                Locale.Get("NaturalDocs.Engine", "Comments.txt.CannotDefineXWhenYIsDefined(x,y)", "Display Name from Locale", "Display Name")
                                );
                        }
                        else
                        {
                            currentCommentType.SetDisplayNameFromLocale(value, file.PropertyLocation);
                        }
                    }
                    else if (pluralDisplayNameFromLocaleRegex.IsMatch(identifier))
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else if (currentCommentType.HasPluralDisplayName)
                        {
                            file.AddError(
                                Locale.Get("NaturalDocs.Engine", "Comments.txt.CannotDefineXWhenYIsDefined(x,y)", "Plural Display Name from Locale", "Plural Display Name")
                                );
                        }
                        else
                        {
                            currentCommentType.SetPluralDisplayNameFromLocale(value, file.PropertyLocation);
                        }
                    }


                    //
                    // Simple Identifier
                    //

                    else if (identifier == "simple identifier")
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else if (nonASCIILettersRegex.IsMatch(value))
                        {
                            file.AddError(
                                Locale.Get("NaturalDocs.Engine", "Comments.txt.SimpleIdentifierMustOnlyBeASCIILetters(name)", value)
                                );
                        }
                        else
                        {
                            currentCommentType.SetSimpleIdentifier(value, file.PropertyLocation);
                        }
                    }


                    //
                    // Scope
                    //

                    else if (identifier == "scope")
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else
                        {
                            value = value.ToLower();

                            if (value == "normal")
                            {
                                currentCommentType.SetScope(CommentType.ScopeValue.Normal, file.PropertyLocation);
                            }
                            else if (startRegex.IsMatch(value))
                            {
                                currentCommentType.SetScope(CommentType.ScopeValue.Start, file.PropertyLocation);
                            }
                            else if (endRegex.IsMatch(value))
                            {
                                currentCommentType.SetScope(CommentType.ScopeValue.End, file.PropertyLocation);
                            }
                            else if (alwaysGlobalRegex.IsMatch(value))
                            {
                                currentCommentType.SetScope(CommentType.ScopeValue.AlwaysGlobal, file.PropertyLocation);
                            }
                            else
                            {
                                file.AddError(
                                    Locale.Get("NaturalDocs.Engine", "Comments.txt.UnrecognizedValue(keyword, value)", "Scope", value)
                                    );
                            }
                        }
                    }


                    //
                    // Flags
                    //

                    else if (flagsRegex.IsMatch(identifier))
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else
                        {
                            value = value.ToLower();

                            if (!string.IsNullOrEmpty(value))
                            {
                                string[] flagStrings = commaSeparatorRegex.Split(value);

                                // Merge them with the existing flags instead of overwriting them since they may also be set by the
                                // Class Hierarchy property.
                                CommentType.FlagValue flagsValue = currentCommentType.Flags ?? default;

                                foreach (string flagString in flagStrings)
                                {
                                    if (flagString == "code")
                                    {
                                        flagsValue |= CommentType.FlagValue.Code;
                                    }
                                    else if (flagString == "file")
                                    {
                                        flagsValue |= CommentType.FlagValue.File;
                                    }
                                    else if (documentationRegex.IsMatch(flagString))
                                    {
                                        flagsValue |= CommentType.FlagValue.Documentation;
                                    }
                                    else if (variableTypeRegex.IsMatch(flagString))
                                    {
                                        flagsValue |= CommentType.FlagValue.VariableType;
                                    }
                                    else if (classHierarchyRegex.IsMatch(flagString))
                                    {
                                        flagsValue |= CommentType.FlagValue.ClassHierarchy;
                                    }
                                    else if (databaseHierarchyRegex.IsMatch(flagString))
                                    {
                                        flagsValue |= CommentType.FlagValue.DatabaseHierarchy;
                                    }
                                    else if (enumRegex.IsMatch(flagString))
                                    {
                                        flagsValue |= CommentType.FlagValue.Enum;
                                    }
                                    else if (string.IsNullOrEmpty(flagString) == false)
                                    {
                                        file.AddError(
                                            Locale.Get("NaturalDocs.Engine", "Comments.txt.UnrecognizedValue(keyword, value)", "Flags", flagString)
                                            );
                                    }
                                }

                                currentCommentType.SetFlags(flagsValue, file.PropertyLocation);
                            }
                        }
                    }


                    //
                    // Class Hierarchy (deprecated, convert to flag)
                    //

                    else if (classHierarchyRegex.IsMatch(identifier))
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else
                        {
                            value = value.ToLower();

                            // Merge it with the existing flags since they may already be set.
                            CommentType.FlagValue flagsValue = currentCommentType.Flags ?? default;

                            if (yesRegex.IsMatch(value))
                            {
                                flagsValue |= CommentType.FlagValue.ClassHierarchy;
                            }
                            else if (noRegex.IsMatch(value))
                            {
                                flagsValue &= ~CommentType.FlagValue.ClassHierarchy;
                            }
                            else
                            {
                                file.AddError(
                                    Locale.Get("NaturalDocs.Engine", "Comments.txt.UnrecognizedValue(keyword, value)", "Class Hierarchy", value)
                                    );
                            }

                            currentCommentType.SetFlags(flagsValue, file.PropertyLocation);
                        }
                    }


                    //
                    // Keywords
                    //

                    else if (keywordsRegex.IsMatch(identifier))
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else
                        {
                            currentKeywordGroup = new TextFileKeywordGroup(file.PropertyLocation);
                            currentCommentType.AddKeywordGroup(currentKeywordGroup);
                            inKeywords = true;

                            if (!string.IsNullOrEmpty(value))
                            {
                                string[] keywordsArray = commaSeparatorRegex.Split(value);

                                foreach (string keyword in keywordsArray)
                                {
                                    int bannedChar = keyword.IndexOfAny(BannedKeywordChars);
                                    if (bannedChar != -1)
                                    {
                                        file.AddError(
                                            Locale.Get("NaturalDocs.Engine", "Comments.txt.KeywordsCannotContain(char)", keyword[bannedChar])
                                            );
                                        // Continue parsing
                                    }

                                    currentKeywordGroup.Add(keyword);
                                }
                            }
                        }
                    }


                    //
                    // Language-Specific Keywords
                    //

                    // This must be tested after ignored and language-general keywords so that their modifiers ("add" or "ignore") don't
                    // get mistaken for language names.
                    else if (languageSpecificKeywordsRegex.IsMatch(identifier))
                    {
                        if (currentCommentType == null)
                        {
                            AddNeedsCommentTypeError(file, identifier);
                        }
                        else
                        {
                            var match        = languageSpecificKeywordsRegex.Match(identifier);
                            var languageName = match.Groups[1].ToString();

                            currentKeywordGroup = new TextFileKeywordGroup(file.PropertyLocation, languageName);
                            currentCommentType.AddKeywordGroup(currentKeywordGroup);
                            inKeywords = true;

                            if (!string.IsNullOrEmpty(value))
                            {
                                string[] keywordsArray = commaSeparatorRegex.Split(value);

                                foreach (string keyword in keywordsArray)
                                {
                                    int bannedChar = keyword.IndexOfAny(BannedKeywordChars);
                                    if (bannedChar != -1)
                                    {
                                        file.AddError(
                                            Locale.Get("NaturalDocs.Engine", "Comments.txt.KeywordsCannotContain(char)", keyword[bannedChar])
                                            );
                                        // Continue parsing
                                    }

                                    currentKeywordGroup.Add(keyword);
                                }
                            }
                        }
                    }


                    //
                    // Deprecated keywords: Can Group With, Page Title if First
                    //

                    else if (identifier == "index" ||
                             identifier == "index with" ||
                             breakListsRegex.IsMatch(identifier) ||
                             identifier == "can group with" ||
                             identifier == "page title if first")
                    {
                        // Ignore and continue
                    }


                    //
                    // Unrecognized keywords
                    //

                    else
                    {
                        file.AddError(
                            Locale.Get("NaturalDocs.Engine", "Comments.txt.UnrecognizedKeyword(keyword)", identifier)
                            );
                    }
                }                          // while (file.Get)

                file.Close();
            }


            if (errorList.Count == previousErrorCount)
            {
                return(true);
            }
            else
            {
                config = null;
                return(false);
            }
        }