using System; using System.Collections.Generic; using AspClassic.Scripting.Utils; namespace AspClassic.Scripting.Runtime; public sealed class DlrConfiguration { private bool _frozen; private readonly bool _debugMode; private readonly bool _privateBinding; private readonly IDictionary _options; public static readonly StringComparer FileExtensionComparer = StringComparer.OrdinalIgnoreCase; public static readonly StringComparer LanguageNameComparer = StringComparer.OrdinalIgnoreCase; public static readonly StringComparer OptionNameComparer = StringComparer.Ordinal; private readonly Dictionary _languageNames; private readonly Dictionary _languageExtensions; private readonly Dictionary _languageConfigurations; private readonly Dictionary _loadedProviderTypes; public bool DebugMode => _debugMode; public bool PrivateBinding => _privateBinding; internal IDictionary Options => _options; internal IDictionary Languages => _languageConfigurations; public DlrConfiguration(bool debugMode, bool privateBinding, IDictionary options) { ContractUtils.RequiresNotNull(options, "options"); _debugMode = debugMode; _privateBinding = privateBinding; _options = options; _languageNames = new Dictionary(LanguageNameComparer); _languageExtensions = new Dictionary(FileExtensionComparer); _languageConfigurations = new Dictionary(); _loadedProviderTypes = new Dictionary(); } public void AddLanguage(string languageTypeName, string displayName, IList names, IList fileExtensions, IDictionary options) { AddLanguage(languageTypeName, displayName, names, fileExtensions, options, null); } internal void AddLanguage(string languageTypeName, string displayName, IList names, IList fileExtensions, IDictionary options, string paramName) { ContractUtils.Requires(!_frozen, "Configuration cannot be modified once the runtime is initialized"); ContractUtils.Requires(names.TrueForAll((string id) => !string.IsNullOrEmpty(id) && !_languageNames.ContainsKey(id)), paramName ?? "names", "Language name should not be null, empty or duplicated between languages"); ContractUtils.Requires(fileExtensions.TrueForAll((string ext) => !string.IsNullOrEmpty(ext) && !_languageExtensions.ContainsKey(ext)), paramName ?? "fileExtensions", "File extension should not be null, empty or duplicated between languages"); ContractUtils.RequiresNotNull(displayName, paramName ?? "displayName"); if (string.IsNullOrEmpty(displayName)) { ContractUtils.Requires(names.Count > 0, paramName ?? "displayName", "Must have a non-empty display name or a a non-empty list of language names"); displayName = names[0]; } AssemblyQualifiedTypeName assemblyQualifiedTypeName = AssemblyQualifiedTypeName.ParseArgument(languageTypeName, paramName ?? "languageTypeName"); if (_languageConfigurations.ContainsKey(assemblyQualifiedTypeName)) { throw new ArgumentException($"Duplicate language with type name '{assemblyQualifiedTypeName}'", "languageTypeName"); } Dictionary dictionary = new Dictionary(_options); foreach (KeyValuePair option in options) { dictionary[option.Key] = option.Value; } LanguageConfiguration value = new LanguageConfiguration(assemblyQualifiedTypeName, displayName, dictionary); _languageConfigurations.Add(assemblyQualifiedTypeName, value); foreach (string name in names) { _languageNames[name] = value; } foreach (string fileExtension in fileExtensions) { _languageExtensions[NormalizeExtension(fileExtension)] = value; } } internal static string NormalizeExtension(string extension) { if (extension[0] != '.') { return "." + extension; } return extension; } internal void Freeze() { _frozen = true; } internal bool TryLoadLanguage(ScriptDomainManager manager, AssemblyQualifiedTypeName providerName, out LanguageContext language) { if (_languageConfigurations.TryGetValue(providerName, out var value)) { language = LoadLanguageContext(manager, value); return true; } language = null; return false; } internal bool TryLoadLanguage(ScriptDomainManager manager, string str, bool isExtension, out LanguageContext language) { Dictionary dictionary = (isExtension ? _languageExtensions : _languageNames); if (dictionary.TryGetValue(str, out var value)) { language = LoadLanguageContext(manager, value); return true; } language = null; return false; } private LanguageContext LoadLanguageContext(ScriptDomainManager manager, LanguageConfiguration config) { bool alreadyLoaded; LanguageContext languageContext = config.LoadLanguageContext(manager, out alreadyLoaded); if (!alreadyLoaded) { lock (_loadedProviderTypes) { Type type = languageContext.GetType(); if (_loadedProviderTypes.TryGetValue(type, out var value)) { throw new InvalidOperationException($"Language implemented by type '{config.ProviderName}' has already been loaded using name '{value.ProviderName}'"); } _loadedProviderTypes.Add(type, config); return languageContext; } } return languageContext; } public string[] GetLanguageNames(LanguageContext context) { ContractUtils.RequiresNotNull(context, "context"); List list = new List(); foreach (KeyValuePair languageName in _languageNames) { if (languageName.Value.LanguageContext == context) { list.Add(languageName.Key); } } return list.ToArray(); } internal string[] GetLanguageNames(LanguageConfiguration config) { List list = new List(); foreach (KeyValuePair languageName in _languageNames) { if (languageName.Value == config) { list.Add(languageName.Key); } } return list.ToArray(); } public string[] GetLanguageNames() { return ArrayUtils.MakeArray(_languageNames.Keys); } public string[] GetFileExtensions(LanguageContext context) { List list = new List(); foreach (KeyValuePair languageExtension in _languageExtensions) { if (languageExtension.Value.LanguageContext == context) { list.Add(languageExtension.Key); } } return list.ToArray(); } internal string[] GetFileExtensions(LanguageConfiguration config) { List list = new List(); foreach (KeyValuePair languageExtension in _languageExtensions) { if (languageExtension.Value == config) { list.Add(languageExtension.Key); } } return list.ToArray(); } public string[] GetFileExtensions() { return ArrayUtils.MakeArray(_languageExtensions.Keys); } internal LanguageConfiguration GetLanguageConfig(LanguageContext context) { foreach (LanguageConfiguration value in _languageConfigurations.Values) { if (value.LanguageContext == context) { return value; } } return null; } }