/* Function: FinalizeLanguage * * Merges the settings of a <ConfigFiles.TextFileLanguage> into a <Language>, returning whether it was successful. If there * are any errors it will add them to the error list. * * - This does not handle file extensions, aliases, or shebang strings as <Language> does not store them. * * - This does not handle prototype enders as comment type names aren't available in <CommentTypes.Manager> until * stage 2. * * - This will check for errors such as defining comment symbols on text files, shebang strings, or languages with full support. * * - This will generate Javadoc and XML comment symbols from the line and block comment symbols for languages with * basic support, assuming they were not already set as part of a predefined language object. * * - This will generate <Language.SimpleIdentifier> if one is not defined, though it may still be null if there are no acceptable * characters in the name, such as if the language name only used Japanese characters. */ protected bool FinalizeLanguage(ref Language baseLanguage, ConfigFiles.TextFileLanguage overridingLanguage, Errors.ErrorList errorList) { #if DEBUG if (baseLanguage.Name.NormalizeKey(Config.KeySettingsForLanguageName) != overridingLanguage.Name.NormalizeKey(Config.KeySettingsForLanguageName)) { throw new Exception("Can't finalize language " + baseLanguage.Name + " with settings for " + overridingLanguage.Name + "."); } #endif if (!ApplyProperties(ref baseLanguage, overridingLanguage, errorList)) { return(false); } GenerateJavadocCommentSymbols(baseLanguage); GenerateXMLCommentSymbols(baseLanguage); if (baseLanguage.SimpleIdentifier == null) { // 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 "LanguageID[number]" but the number isn't determind yet, so leave it as null for now. string simpleIdentifier = baseLanguage.Name.OnlyAToZ(); if (!string.IsNullOrEmpty(simpleIdentifier)) { baseLanguage.SimpleIdentifier = simpleIdentifier; } } return(true); }
/* Function: ApplyProperties * * Merges the settings of a <ConfigFiles.TextFileLanguage> into a <Language>, returning whether it was successful. If there * are any errors it will add them to the error list. * * - This does not handle file extensions, aliases, or shebang strings as <Language> does not store them. * * - This does not handle prototype enders as comment type names aren't available in <CommentTypes.Manager> until * stage 2. * * - This will check for errors such as defining comment symbols on text files, shebang strings, or languages with full support. */ protected bool ApplyProperties(ref Language baseLanguage, ConfigFiles.TextFileLanguage overridingLanguage, Errors.ErrorList errorList) { int originalErrorCount = errorList.Count; if (overridingLanguage.HasSimpleIdentifier) { baseLanguage.SimpleIdentifier = overridingLanguage.SimpleIdentifier; } if (overridingLanguage.HasLineCommentSymbols && CheckForBasicLanguageSupport(baseLanguage, "Line Comments", overridingLanguage.LineCommentSymbolsPropertyLocation, errorList)) { baseLanguage.LineCommentSymbols = overridingLanguage.LineCommentSymbols; } if (overridingLanguage.HasBlockCommentSymbols && CheckForBasicLanguageSupport(baseLanguage, "Block Comments", overridingLanguage.BlockCommentSymbolsPropertyLocation, errorList)) { baseLanguage.BlockCommentSymbols = overridingLanguage.BlockCommentSymbols; } if (overridingLanguage.HasMemberOperator && (baseLanguage.Type == Language.LanguageType.TextFile || CheckForBasicLanguageSupport(baseLanguage, "Member Operator", overridingLanguage.MemberOperatorPropertyLocation, errorList))) { baseLanguage.MemberOperator = overridingLanguage.MemberOperator; } // Skip prototype enders in stage 1 if (overridingLanguage.HasLineExtender && CheckForBasicLanguageSupport(baseLanguage, "LineExtender", overridingLanguage.LineExtenderPropertyLocation, errorList)) { baseLanguage.LineExtender = overridingLanguage.LineExtender; } if (overridingLanguage.HasEnumValues && (baseLanguage.Type == Language.LanguageType.TextFile || CheckForBasicLanguageSupport(baseLanguage, "Enum Values", overridingLanguage.EnumValuesPropertyLocation, errorList))) { baseLanguage.EnumValue = (Language.EnumValues)overridingLanguage.EnumValues; } if (overridingLanguage.HasCaseSensitive && (baseLanguage.Type == Language.LanguageType.TextFile || CheckForBasicLanguageSupport(baseLanguage, "Case Sensitive", overridingLanguage.CaseSensitivePropertyLocation, errorList))) { baseLanguage.CaseSensitive = (bool)overridingLanguage.CaseSensitive; } return(errorList.Count == originalErrorCount); }
/* Function: AddLanguage * Adds a language to the file. */ public void AddLanguage(TextFileLanguage language) { if (languages == null) { languages = new List <TextFileLanguage>(); } languages.Add(language); }
/* Function: ValidateLanguage * Validates all the settings in a <ConfigFiles.TextFileLanguage>. Returns whether it is valid, and adds any errors it finds to * errorList. */ protected bool ValidateLanguage(ConfigFiles.TextFileLanguage language, Errors.ErrorList errorList) { // TextFileParser should have already normalized file extensions, so entering ".txt" or "*.txt" is converted to just "txt". // TextFileParser should have already validated that Simple Identifier only contains acceptable characters. // We'll check for Alter Language entries not matching an existing one when we merge languages. // We'll check for basic language support entries being applied to languages with full support when we merge languages. // So I guess there's nothing to do then! return(true); }
/* Function: Duplicate * Creates an independent copy of the language and all its properties. */ public TextFileLanguage Duplicate() { TextFileLanguage copy = new TextFileLanguage(name, namePropertyLocation, alterLanguage); copy.simpleIdentifier = simpleIdentifier; copy.simpleIdentifierPropertyLocation = simpleIdentifierPropertyLocation; if (aliases != null) { copy.aliases = new List <string>(aliases.Count); copy.aliases.AddRange(aliases); } copy.aliasesPropertyChange = aliasesPropertyChange; copy.aliasesPropertyLocation = aliasesPropertyLocation; if (fileExtensions != null) { copy.fileExtensions = new List <string>(fileExtensions.Count); copy.fileExtensions.AddRange(fileExtensions); } copy.fileExtensionsPropertyChange = fileExtensionsPropertyChange; copy.fileExtensionsPropertyLocation = fileExtensionsPropertyLocation; if (shebangStrings != null) { copy.shebangStrings = new List <string>(shebangStrings.Count); copy.shebangStrings.AddRange(shebangStrings); } copy.shebangStringsPropertyChange = shebangStringsPropertyChange; copy.shebangStringsPropertyLocation = shebangStringsPropertyLocation; if (lineCommentSymbols != null) { copy.lineCommentSymbols = new List <string>(lineCommentSymbols.Count); copy.lineCommentSymbols.AddRange(lineCommentSymbols); } copy.lineCommentSymbolsPropertyLocation = lineCommentSymbolsPropertyLocation; if (blockCommentSymbols != null) { copy.blockCommentSymbols = new List <BlockCommentSymbols>(blockCommentSymbols.Count); copy.blockCommentSymbols.AddRange(blockCommentSymbols); // works because it's a struct } copy.blockCommentSymbolsPropertyLocation = blockCommentSymbolsPropertyLocation; copy.memberOperator = memberOperator; copy.memberOperatorPropertyLocation = memberOperatorPropertyLocation; if (prototypeEnders != null) { copy.prototypeEnders = new List <TextFilePrototypeEnders>(); foreach (var prototypeEnder in prototypeEnders) { copy.prototypeEnders.Add(prototypeEnder.Duplicate()); } } copy.lineExtender = lineExtender; copy.lineExtenderPropertyLocation = lineExtenderPropertyLocation; copy.enumValues = enumValues; copy.enumValuesPropertyLocation = enumValuesPropertyLocation; copy.caseSensitive = caseSensitive; copy.caseSensitivePropertyLocation = caseSensitivePropertyLocation; return(copy); }
/* Function: MergeLanguageInto * Merges the settings of a <ConfigFiles.TextFileLanguage> into another one, overriding the settings of the first. This does * NOT cover file extensions, aliases, or shebang strings. The base object will be altered. */ protected void MergeLanguageInto(ref ConfigFiles.TextFileLanguage baseLanguage, ConfigFiles.TextFileLanguage overridingLanguage) { // Leave Name and PropertyLocation alone. We'll keep the base's. // Leave AlterLanguage alone. The base should be false and the overriding should be true, and we want the end // result to be false. if (overridingLanguage.HasSimpleIdentifier) { baseLanguage.SetSimpleIdentifier(overridingLanguage.SimpleIdentifier, overridingLanguage.SimpleIdentifierPropertyLocation); } // Ignore Aliases // Ignore FileExtensions // Ignore ShebangStrings if (overridingLanguage.HasLineCommentSymbols) { baseLanguage.SetLineCommentSymbols(overridingLanguage.LineCommentSymbols, overridingLanguage.LineCommentSymbolsPropertyLocation); } if (overridingLanguage.HasBlockCommentSymbols) { baseLanguage.SetBlockCommentSymbols(overridingLanguage.BlockCommentSymbols, overridingLanguage.BlockCommentSymbolsPropertyLocation); } if (overridingLanguage.HasMemberOperator) { baseLanguage.SetMemberOperator(overridingLanguage.MemberOperator, overridingLanguage.MemberOperatorPropertyLocation); } if (overridingLanguage.HasPrototypeEnders) { foreach (var prototypeEnders in overridingLanguage.PrototypeEnders) { // This will automatically overwrite any ender with the same comment type. baseLanguage.AddPrototypeEnders(prototypeEnders); } } if (overridingLanguage.HasLineExtender) { baseLanguage.SetLineExtender(overridingLanguage.LineExtender, overridingLanguage.LineExtenderPropertyLocation); } if (overridingLanguage.HasEnumValues) { baseLanguage.SetEnumValues(overridingLanguage.EnumValues, overridingLanguage.EnumValuesPropertyLocation); } if (overridingLanguage.HasCaseSensitive) { baseLanguage.SetCaseSensitive(overridingLanguage.CaseSensitive, overridingLanguage.CaseSensitivePropertyLocation); } }
// 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); } }