// Group: Loading Functions // __________________________________________________________________________ /* Function: Load * Attempts to load any project information it can find in <Menu.txt>. Unlike <Project_txt.Load()>, this doesn't take an * <ErrorList>. Loading <Menu.txt> is done when it can provide a benefit but it failing to load is not an error. */ public bool Load(Path path, out ProjectConfig projectConfig) { projectConfig = new ProjectConfig(PropertySource.OldMenuFile); using (var configFile = new ConfigFile()) { ErrorList ignored = new Errors.ErrorList(); bool openResult = configFile.Open(path, PropertySource.OldMenuFile, ConfigFile.FileFormatFlags.CondenseIdentifierWhitespace | ConfigFile.FileFormatFlags.SupportsBraces | ConfigFile.FileFormatFlags.MakeIdentifiersLowercase, ignored); if (openResult == false) { return(false); } Regex.Config.Subtitle subtitleRegex = new Regex.Config.Subtitle(); Regex.Config.Timestamp timestampRegex = new Regex.Config.Timestamp(); string lcIdentifier, value; while (configFile.Get(out lcIdentifier, out value)) { var propertyLocation = new PropertyLocation(PropertySource.OldMenuFile, path, configFile.LineNumber); if (lcIdentifier == "title") { projectConfig.ProjectInfo.Title = value.ConvertCopyrightAndTrademark(); projectConfig.ProjectInfo.TitlePropertyLocation = propertyLocation; } else if (subtitleRegex.IsMatch(lcIdentifier)) { projectConfig.ProjectInfo.Subtitle = value.ConvertCopyrightAndTrademark(); projectConfig.ProjectInfo.SubtitlePropertyLocation = propertyLocation; } else if (lcIdentifier == "footer" || lcIdentifier == "copyright") { projectConfig.ProjectInfo.Copyright = value.ConvertCopyrightAndTrademark(); projectConfig.ProjectInfo.CopyrightPropertyLocation = propertyLocation; } else if (timestampRegex.IsMatch(lcIdentifier)) { projectConfig.ProjectInfo.TimestampCode = value; projectConfig.ProjectInfo.TimestampCodePropertyLocation = propertyLocation; } // Otherwise just ignore the entry. } configFile.Close(); } return(true); }
// Group: Loading Functions // __________________________________________________________________________ /* Function: Load * Attempts to parse <Project.txt> and return it as a <ProjectConfig>. Any syntax errors found will be added to the * <ErrorList>. The <ProjectConfig> object will always exist, even if all its properties are empty. */ public bool Load(Path path, out ProjectConfig projectConfig, ErrorList errorList) { projectConfig = new ProjectConfig(PropertySource.ProjectFile); this.errorList = errorList; this.projectConfig = projectConfig; int originalErrorCount = errorList.Count; using (var configFile = new ConfigFile()) { // We don't condense value whitespace because some things like title, subtitle, and copyright may want multiple spaces. bool openResult = configFile.Open(path, PropertySource.ProjectFile, ConfigFile.FileFormatFlags.CondenseIdentifierWhitespace | ConfigFile.FileFormatFlags.MakeIdentifiersLowercase, errorList); if (openResult == false) { return(false); } string lcIdentifier, value; Target currentTarget = null; ProjectInfo currentProjectInfo = projectConfig.ProjectInfo; while (configFile.Get(out lcIdentifier, out value)) { var propertyLocation = new PropertyLocation(PropertySource.ProjectFile, configFile.FileName, configFile.LineNumber); Target target = null; if (GetGlobalProperty(lcIdentifier, value, propertyLocation)) { currentTarget = null; currentProjectInfo = projectConfig.ProjectInfo; } else if (GetTargetHeader(lcIdentifier, value, propertyLocation, out target)) { currentTarget = target; if (target is Targets.Output) { currentProjectInfo = (target as Targets.Output).ProjectInfo; } else { currentProjectInfo = projectConfig.ProjectInfo; } } else if (GetProjectInfoProperty(lcIdentifier, value, propertyLocation, currentProjectInfo)) { } else if (currentTarget != null && GetTargetProperty(lcIdentifier, value, propertyLocation, currentTarget)) { } else { errorList.Add( message: Locale.Get("NaturalDocs.Engine", "ConfigFile.NotAValidIdentifier(identifier)", lcIdentifier), propertyLocation: propertyLocation ); } } configFile.Close(); } return(errorList.Count == originalErrorCount); }
// Group: Loading Functions // __________________________________________________________________________ /* Function: Load * * Loads the contents of a <Languages.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.MakeIdentifiersLowercase, errorList); if (openResult == false) { config = null; return(false); } config = new ConfigFiles.TextFile(); TextFileLanguage currentLanguage = null; char[] space = { ' ' }; System.Text.RegularExpressions.Match match; while (file.Get(out string identifier, out string value)) { // // Ignore Extensions // if (ignoreExtensionsRegex.IsMatch(identifier)) { currentLanguage = null; var ignoredExtensions = value.Split(space); NormalizeFileExtensions(ignoredExtensions); config.AddIgnoredFileExtensions(ignoredExtensions, file.PropertyLocation); } // // Language // else if (identifier == "language") { var existingLanguage = config.FindLanguage(value); if (existingLanguage != null) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.LanguageAlreadyExists(name)", value) ); // Continue parsing. We'll throw this into the existing language even though it shouldn't be overwriting // its values because we want to find any other errors there are in the file. currentLanguage = existingLanguage; } else { currentLanguage = new TextFileLanguage(value, file.PropertyLocation); config.AddLanguage(currentLanguage); } } // // Alter Language // else if (alterLanguageRegex.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: // // Language: Language A // Extensions: langA // // Language: Language B // Extensions: langB // // Alter Language: Language A // Add Extensions: langB // // Extension langB should be part of Language A. However, if we merged the definitions it would appear // first and be overridden by Language B. So we just create two language entries for A instead. currentLanguage = new TextFileLanguage(value, file.PropertyLocation, alterLanguage: true); config.AddLanguage(currentLanguage); } // // Aliases // else if (aliasesRegex.IsMatch(identifier)) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else if (currentLanguage.AlterLanguage) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.NeedAddReplaceWhenAlteringLanguage(keyword)", "Aliases") ); } else { var aliases = value.Split(space); currentLanguage.SetAliases(aliases, file.PropertyLocation); } } // // Add/Replace Aliases // else if ((match = addReplaceAliasesRegex.Match(identifier)) != null && match.Success) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { TextFileLanguage.PropertyChange propertyChange; if (match.Groups[1].Value == "add") { propertyChange = TextFileLanguage.PropertyChange.Add; } else if (match.Groups[1].Value == "replace") { propertyChange = TextFileLanguage.PropertyChange.Replace; } else { throw new NotImplementedException(); } // If we're adding to a language that already has them, we need to combine the properties. if (propertyChange == TextFileLanguage.PropertyChange.Add && currentLanguage.HasAliases) { var oldAliases = currentLanguage.Aliases; var newAliases = value.Split(space); List <string> combinedAliases = new List <string>(oldAliases.Count + newAliases.Length); combinedAliases.AddRange(oldAliases); combinedAliases.AddRange(newAliases); currentLanguage.SetAliases(combinedAliases, file.PropertyLocation, propertyChange); } // Otherwise we can just add them as is. else { var aliases = value.Split(space); currentLanguage.SetAliases(aliases, file.PropertyLocation, propertyChange); } } } // // File Extensions // else if (fileExtensionsRegex.IsMatch(identifier)) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else if (currentLanguage.AlterLanguage) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.NeedAddReplaceWhenAlteringLanguage(keyword)", "Extensions") ); } else { var extensions = value.Split(space); NormalizeFileExtensions(extensions); currentLanguage.SetFileExtensions(extensions, file.PropertyLocation); } } // // Add/Replace File Extensions // else if ((match = addReplaceExtensionsRegex.Match(identifier)) != null && match.Success) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { TextFileLanguage.PropertyChange propertyChange; if (match.Groups[1].Value == "add") { propertyChange = TextFileLanguage.PropertyChange.Add; } else if (match.Groups[1].Value == "replace") { propertyChange = TextFileLanguage.PropertyChange.Replace; } else { throw new NotImplementedException(); } // If we're adding to a language that already has them, we need to combine the properties. if (propertyChange == TextFileLanguage.PropertyChange.Add && currentLanguage.HasFileExtensions) { var oldExtensions = currentLanguage.FileExtensions; var newExtensions = value.Split(space); NormalizeFileExtensions(newExtensions); List <string> combinedExtensions = new List <string>(oldExtensions.Count + newExtensions.Length); combinedExtensions.AddRange(oldExtensions); combinedExtensions.AddRange(newExtensions); currentLanguage.SetFileExtensions(combinedExtensions, file.PropertyLocation, propertyChange); } // Otherwise we can just add them as is. else { var extensions = value.Split(space); NormalizeFileExtensions(extensions); currentLanguage.SetFileExtensions(extensions, file.PropertyLocation, propertyChange); } } } // // Shebang Strings // else if (shebangStringsRegex.IsMatch(identifier)) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else if (currentLanguage.AlterLanguage) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.NeedAddReplaceWhenAlteringLanguage(keyword)", "Shebang Strings") ); } else { var shebangStrings = value.Split(space); currentLanguage.SetShebangStrings(shebangStrings, file.PropertyLocation); } } // // Add/Replace Shebang Strings // else if ((match = addReplaceShebangStringsRegex.Match(identifier)) != null && match.Success) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { TextFileLanguage.PropertyChange propertyChange; if (match.Groups[1].Value == "add") { propertyChange = TextFileLanguage.PropertyChange.Add; } else if (match.Groups[1].Value == "replace") { propertyChange = TextFileLanguage.PropertyChange.Replace; } else { throw new NotImplementedException(); } // If we're adding to a language that already has them, we need to combine the properties. if (propertyChange == TextFileLanguage.PropertyChange.Add && currentLanguage.HasShebangStrings) { var oldShebangStrings = currentLanguage.ShebangStrings; var newShebangStrings = value.Split(space); List <string> combinedShebangStrings = new List <string>(oldShebangStrings.Count + newShebangStrings.Length); combinedShebangStrings.AddRange(oldShebangStrings); combinedShebangStrings.AddRange(newShebangStrings); currentLanguage.SetShebangStrings(combinedShebangStrings, file.PropertyLocation, propertyChange); } // Otherwise we can just add them as is. else { var shebangStrings = value.Split(space); currentLanguage.SetShebangStrings(shebangStrings, file.PropertyLocation, propertyChange); } } } // // Simple Identifier // else if (identifier == "simple identifier") { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else if (nonASCIILettersRegex.IsMatch(value)) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.SimpleIdentifierMustOnlyBeASCIILetters(name)", value) ); } else { currentLanguage.SetSimpleIdentifier(value, file.PropertyLocation); } } // // Line Comments // else if (lineCommentsRegex.IsMatch(identifier)) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { var lineCommentSymbols = value.Split(space); currentLanguage.SetLineCommentSymbols(lineCommentSymbols, file.PropertyLocation); } } // // Block Comments // else if (blockCommentsRegex.IsMatch(identifier)) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { var blockCommentStrings = value.Split(space); if (blockCommentStrings.Length % 2 != 0) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.BlockCommentsMustHaveAnEvenNumberOfSymbols") ); } else { List <BlockCommentSymbols> blockCommentSymbols = new List <BlockCommentSymbols>(blockCommentStrings.Length / 2); for (int i = 0; i < blockCommentStrings.Length; i += 2) { blockCommentSymbols.Add( new BlockCommentSymbols(blockCommentStrings[i], blockCommentStrings[i + 1]) ); } currentLanguage.SetBlockCommentSymbols(blockCommentSymbols, file.PropertyLocation); } } } // // Member Operator // else if (memberOperatorRegex.IsMatch(identifier)) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { currentLanguage.SetMemberOperator(value, file.PropertyLocation); } } // // Line Extender // else if (identifier == "line extender") { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { currentLanguage.SetLineExtender(value, file.PropertyLocation); } } // // Enum Values // else if (enumValuesRegex.IsMatch(identifier)) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { string lcValue = value.ToLower(); if (lcValue == "global") { currentLanguage.SetEnumValues(Language.EnumValues.Global, file.PropertyLocation); } else if (lcValue == "under type") { currentLanguage.SetEnumValues(Language.EnumValues.UnderType, file.PropertyLocation); } else if (lcValue == "under parent") { currentLanguage.SetEnumValues(Language.EnumValues.UnderParent, file.PropertyLocation); } else { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.InvalidEnumValue(value)", value) ); } } } // // Case Sensitive // else if (caseSensitiveRegex.IsMatch(identifier)) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { string lcValue = value.ToLower(); if (yesRegex.IsMatch(lcValue)) { currentLanguage.SetCaseSensitive(true, file.PropertyLocation); } else if (noRegex.IsMatch(lcValue)) { currentLanguage.SetCaseSensitive(false, file.PropertyLocation); } else { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.UnrecognizedValue(keyword, value)", "Case Sensitive", value) ); } } } // // Prototype Enders // // Use identifier and not lcIdentifier to keep the case of the comment type. The regex will compensate. else if ((match = prototypeEndersRegex.Match(identifier)) != null && match.Success) { if (currentLanguage == null) { AddNeedsLanguageError(file, identifier); } else { string commentType = match.Groups[1].Value; string[] enderStrings = value.Split(space); var enders = new TextFilePrototypeEnders(commentType, file.PropertyLocation); enders.AddEnderStrings(enderStrings); currentLanguage.AddPrototypeEnders(enders); } } // // Deprecated keywords // else if (ignorePrefixesRegex.IsMatch(identifier) || identifier == "perl package" || identifier == "full language support") { // Ignore } // // Unrecognized keywords // else { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.UnrecognizedKeyword(keyword)", identifier) ); } } // while (file.Get) file.Close(); } if (errorList.Count == previousErrorCount) { return(true); } else { return(false); } }
// Group: Loading Functions // __________________________________________________________________________ /* Function: Load * * Loads the configuration file and parses it. Redundant information will be simplified out, such as an Alter * Language section that applies to a language defined in the same file. * * Parameters: * * filename - The <Path> where the file is located. * fileLanguages - Returns a list of <ConfigFileLanguages> in no particular order. * fileIgnoredExtensions - Returns any ignored extensions as a string array. * errorList - If it couldn't successfully parse the file it will add error messages to this list. * * Returns: * * Whether it was able to successfully load and parse the file without any errors. */ public bool Load(Path filename, out List <ConfigFileLanguage> fileLanguages, out List <string> fileIgnoredExtensions, Errors.ErrorList errorList) { fileLanguages = new List <ConfigFileLanguage>(); fileIgnoredExtensions = new List <string>(); StringTable <ConfigFileLanguage> fileLanguageNames = new StringTable <ConfigFileLanguage>(Engine.Languages.Manager.KeySettingsForLanguageName); int previousErrorCount = errorList.Count; using (ConfigFile file = new ConfigFile()) { // Can't make identifiers lowercase here or we'd lose the case of the comment type in prototype ender lines. bool openResult = file.Open(filename, ConfigFile.FileFormatFlags.CondenseIdentifierWhitespace | ConfigFile.FileFormatFlags.CondenseValueWhitespace, errorList); if (openResult == false) { return(false); } string identifier, lcIdentifier, value; ConfigFileLanguage currentLanguage = null; // We need this in addition to ConfigFileLanguage.AlterLanguage because an entry altering a type defined in the // same file would be combined into the original, yet we still need to know if that entry is Alter to properly // detect whether we need to use Add/Replace with certain properties. bool alterCurrentLanguage = false; char[] space = { ' ' }; System.Text.RegularExpressions.Match match; while (file.Get(out identifier, out value)) { lcIdentifier = identifier.ToLower(); // // Ignore Extensions // if (ignoreExtensionsRegex.IsMatch(lcIdentifier)) { currentLanguage = null; string[] ignoredExtensionsArray = value.Split(space); fileIgnoredExtensions.AddRange(ignoredExtensionsArray); } // // Language // else if (lcIdentifier == "language") { if (fileLanguageNames.ContainsKey(value)) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.LanguageAlreadyExists(name)", value) ); // Continue parsing. We'll throw this into the existing language even though it shouldn't be overwriting // its values because we want to find any other errors there are in the file. currentLanguage = fileLanguageNames[value]; alterCurrentLanguage = false; } else { currentLanguage = new ConfigFileLanguage(value, false, file.LineNumber); alterCurrentLanguage = false; fileLanguages.Add(currentLanguage); fileLanguageNames.Add(value, currentLanguage); } } // // Alter Language // else if (alterLanguageRegex.IsMatch(lcIdentifier)) { // If this language already exists, collapse it into the current definition. if (fileLanguageNames.ContainsKey(value)) { currentLanguage = fileLanguageNames[value]; alterCurrentLanguage = true; } // If it doesn't exist, create the new language anyway with the alter flag set because it may exist in another // file. else { currentLanguage = new ConfigFileLanguage(value, true, file.LineNumber); alterCurrentLanguage = true; fileLanguages.Add(currentLanguage); fileLanguageNames.Add(value, currentLanguage); } } // // Aliases // else if (aliasesRegex.IsMatch(lcIdentifier)) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (alterCurrentLanguage == true) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.NeedAddReplaceWhenAlteringLanguage(keyword)", "Aliases") ); } else { currentLanguage.Aliases = value.Split(space); currentLanguage.AddAliases = false; } } // // Add/Replace Aliases // else if ((match = addReplaceAliasesRegex.Match(lcIdentifier)) != null && match.Success) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (alterCurrentLanguage == true && match.Groups[1].Value == "add" && currentLanguage.Aliases != null) { string[] addAliases = value.Split(space); string[] newAliases = new string[addAliases.Length + currentLanguage.Aliases.Length]; currentLanguage.Aliases.CopyTo(newAliases, 0); addAliases.CopyTo(newAliases, currentLanguage.Aliases.Length); currentLanguage.Aliases = newAliases; currentLanguage.AddAliases = true; } // Covers "replace" when altering a language, "add" and "replace" when not altering a language (no point // in adding an error when we can just tolerate it, and "replace" when altering a language that doesn't have // anything defined. else { currentLanguage.Aliases = value.Split(space); currentLanguage.AddAliases = (match.Groups[1].Value == "add"); } } // // Extensions // else if (extensionsRegex.IsMatch(lcIdentifier)) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (alterCurrentLanguage == true) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.NeedAddReplaceWhenAlteringLanguage(keyword)", "Extensions") ); } else { currentLanguage.Extensions = value.Split(space); currentLanguage.AddExtensions = false; } } // // Add/Replace Extensions // else if ((match = addReplaceExtensionsRegex.Match(lcIdentifier)) != null && match.Success) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (alterCurrentLanguage == true && match.Groups[1].Value == "add" && currentLanguage.Extensions != null) { string[] addExtensions = value.Split(space); string[] newExtensions = new string[addExtensions.Length + currentLanguage.Extensions.Length]; currentLanguage.Extensions.CopyTo(newExtensions, 0); addExtensions.CopyTo(newExtensions, currentLanguage.Extensions.Length); currentLanguage.Extensions = newExtensions; currentLanguage.AddExtensions = true; } // Covers "replace" when altering a language, "add" and "replace" when not altering a language (no point // in adding an error when we can just tolerate it, and "replace" when altering a language that doesn't have // anything defined. else { currentLanguage.Extensions = value.Split(space); currentLanguage.AddExtensions = (match.Groups[1].Value == "add"); } } // // Shebang Strings // else if (shebangStringsRegex.IsMatch(lcIdentifier)) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (alterCurrentLanguage == true) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.NeedAddReplaceWhenAlteringLanguage(keyword)", "Shebang Strings") ); } else { currentLanguage.ShebangStrings = value.Split(space); currentLanguage.AddShebangStrings = false; } } // // Add/Replace Shebang Strings // else if ((match = addReplaceShebangStringsRegex.Match(lcIdentifier)) != null && match.Success) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (alterCurrentLanguage == true && match.Groups[1].Value == "add" && currentLanguage.ShebangStrings != null) { string[] addShebangStrings = value.Split(space); string[] newShebangStrings = new string[addShebangStrings.Length + currentLanguage.ShebangStrings.Length]; currentLanguage.ShebangStrings.CopyTo(newShebangStrings, 0); addShebangStrings.CopyTo(newShebangStrings, currentLanguage.ShebangStrings.Length); currentLanguage.ShebangStrings = newShebangStrings; currentLanguage.AddShebangStrings = true; } // Covers "replace" when altering a language, "add" and "replace" when not altering a language (no point // in adding an error when we can just tolerate it, and "replace" when altering a language that doesn't have // anything defined. else { currentLanguage.ShebangStrings = value.Split(space); currentLanguage.AddShebangStrings = (match.Groups[1].Value == "add"); } } // // Simple Identifier // else if (lcIdentifier == "simple identifier") { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (nonASCIILettersRegex.IsMatch(value)) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.SimpleIdentifierMustOnlyBeASCIILetters(name)", value) ); } else { currentLanguage.SimpleIdentifier = value; } } // // Line Comments // else if (lineCommentsRegex.IsMatch(lcIdentifier)) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else { currentLanguage.LineCommentStrings = value.Split(space); } } // // Block Comments // else if (blockCommentsRegex.IsMatch(lcIdentifier)) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else { string[] newBlockCommentStrings = value.Split(space); if (newBlockCommentStrings.Length % 2 != 0) { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.BlockCommentsMustHaveAnEvenNumberOfSymbols") ); } else { currentLanguage.BlockCommentStringPairs = newBlockCommentStrings; } } } // // Member Operator // else if (memberOperatorRegex.IsMatch(lcIdentifier)) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else { currentLanguage.MemberOperator = value; } } // // Line Extender // else if (lcIdentifier == "line extender") { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else { currentLanguage.LineExtender = value; } } // // Enum Values // else if (enumValuesRegex.IsMatch(lcIdentifier)) { string lcValue = value.ToLower(); if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (lcValue == "global") { currentLanguage.EnumValue = Language.EnumValues.Global; } else if (lcValue == "under type") { currentLanguage.EnumValue = Language.EnumValues.UnderType; } else if (lcValue == "under parent") { currentLanguage.EnumValue = Language.EnumValues.UnderParent; } else { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.InvalidEnumValue(value)", value) ); } } // // Case Sensitive // else if (caseSensitiveRegex.IsMatch(lcIdentifier)) { string lcValue = value.ToLower(); if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else if (yesRegex.IsMatch(lcValue)) { currentLanguage.CaseSensitive = true; } else if (noRegex.IsMatch(lcValue)) { currentLanguage.CaseSensitive = false; } else { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.UnrecognizedValue(keyword, value)", "Case Sensitive", value) ); } } // // Prototype Enders // // Use identifier and not lcIdentifier to keep the case of the comment type. The regex will compensate. else if ((match = prototypeEndersRegex.Match(identifier)) != null && match.Success) { if (currentLanguage == null) { NeedsLanguageError(file, identifier); } else { string commentType = match.Groups[1].Value; string[] enderStrings = value.Split(space); currentLanguage.SetPrototypeEnderStrings(commentType, enderStrings); } } // // Deprecated keywords // else if (ignorePrefixesRegex.IsMatch(lcIdentifier) || lcIdentifier == "perl package" || lcIdentifier == "full language support") { // Ignore } // // Unrecognized keywords // else { file.AddError( Locale.Get("NaturalDocs.Engine", "Languages.txt.UnrecognizedKeyword(keyword)", identifier) ); } } // while (file.Get) file.Close(); } if (errorList.Count == previousErrorCount) { return(true); } else { return(false); } }
// 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); } }