#include "storm/settings/SettingsManager.h" #include <cstring> #include <mutex> #include <iomanip> #include <fstream> #include <regex> #include <set> #include <boost/algorithm/string.hpp> #include <boost/io/ios_state.hpp> #include "storm/exceptions/IllegalFunctionCallException.h" #include "storm/exceptions/OptionParserException.h" #include "storm/utility/storm-version.h" #include "storm/settings/modules/GeneralSettings.h" #include "storm/settings/modules/CoreSettings.h" #include "storm/settings/modules/IOSettings.h" #include "storm/settings/modules/ModelCheckerSettings.h" #include "storm/settings/modules/DebugSettings.h" #include "storm/settings/modules/CuddSettings.h" #include "storm/settings/modules/BuildSettings.h" #include "storm/settings/modules/SylvanSettings.h" #include "storm/settings/modules/EigenEquationSolverSettings.h" #include "storm/settings/modules/GmmxxEquationSolverSettings.h" #include "storm/settings/modules/NativeEquationSolverSettings.h" #include "storm/settings/modules/EliminationSettings.h" #include "storm/settings/modules/MinMaxEquationSolverSettings.h" #include "storm/settings/modules/GameSolverSettings.h" #include "storm/settings/modules/BisimulationSettings.h" #include "storm/settings/modules/GlpkSettings.h" #include "storm/settings/modules/GurobiSettings.h" #include "storm/settings/modules/Smt2SmtSolverSettings.h" #include "storm/settings/modules/TopologicalEquationSolverSettings.h" #include "storm/settings/modules/ExplorationSettings.h" #include "storm/settings/modules/ResourceSettings.h" #include "storm/settings/modules/AbstractionSettings.h" #include "storm/settings/modules/JaniExportSettings.h" #include "storm/settings/modules/JitBuilderSettings.h" #include "storm/settings/modules/MultiObjectiveSettings.h" #include "storm/settings/modules/MultiplierSettings.h" #include "storm/utility/macros.h" #include "storm/utility/file.h" #include "storm/settings/Option.h" namespace storm { namespace settings { SettingsManager::SettingsManager() : modules(), longNameToOptions(), shortNameToOptions(), moduleOptions() { } SettingsManager::~SettingsManager() { // Intentionally left empty. } SettingsManager& SettingsManager::manager() { static SettingsManager settingsManager; return settingsManager; } void SettingsManager::setName(std::string const& name, std::string const& executableName) { this->name = name; this->executableName = executableName; } void SettingsManager::setFromCommandLine(int const argc, char const * const argv[]) { // We convert the arguments to a vector of strings and strip off the first element since it refers to the // name of the program. std::vector<std::string> argumentVector(argc - 1); for (int i = 1; i < argc; ++i) { argumentVector[i - 1] = std::string(argv[i]); } this->setFromExplodedString(argumentVector); } void SettingsManager::setFromString(std::string const& commandLineString) { if (commandLineString.empty()) { this->setFromExplodedString({}); } else { std::vector<std::string> argumentVector; boost::split(argumentVector, commandLineString, boost::is_any_of("\t ")); this->setFromExplodedString(argumentVector); } } void SettingsManager::setFromExplodedString(std::vector<std::string> const& commandLineArguments) { // In order to assign the parsed arguments to an option, we need to keep track of the "active" option's name. bool optionActive = false; bool activeOptionIsShortName = false; std::string activeOptionName = ""; std::vector<std::string> argumentCache; // Walk through all arguments. for (uint_fast64_t i = 0; i < commandLineArguments.size(); ++i) { std::string const& currentArgument = commandLineArguments[i]; // Check if the given argument is a new option or belongs to a previously given option. if (!currentArgument.empty() && currentArgument.at(0) == '-') { if (optionActive) { // At this point we know that a new option is about to come. Hence, we need to assign the current // cache content to the option that was active until now. setOptionsArguments(activeOptionName, activeOptionIsShortName ? this->shortNameToOptions : this->longNameToOptions, argumentCache); // After the assignment, the argument cache needs to be cleared. argumentCache.clear(); } else { optionActive = true; } if (currentArgument.at(1) == '-') { // In this case, the argument has to be the long name of an option. Try to get all options that // match the long name. std::string optionName = currentArgument.substr(2); auto optionIterator = this->longNameToOptions.find(optionName); STORM_LOG_THROW(optionIterator != this->longNameToOptions.end(), storm::exceptions::OptionParserException, "Unknown option '" << optionName << "'."); activeOptionIsShortName = false; activeOptionName = optionName; } else { // In this case, the argument has to be the short name of an option. Try to get all options that // match the short name. std::string optionName = currentArgument.substr(1); auto optionIterator = this->shortNameToOptions.find(optionName); STORM_LOG_THROW(optionIterator != this->shortNameToOptions.end(), storm::exceptions::OptionParserException, "Unknown option '" << optionName << "'."); activeOptionIsShortName = true; activeOptionName = optionName; } } else if (optionActive) { // Add the current argument to the list of arguments for the currently active options. argumentCache.push_back(currentArgument); } else { STORM_LOG_THROW(false, storm::exceptions::OptionParserException, "Found stray argument '" << currentArgument << "' that is not preceeded by a matching option."); } } // If an option is still active at this point, we need to set it. if (optionActive) { setOptionsArguments(activeOptionName, activeOptionIsShortName ? this->shortNameToOptions : this->longNameToOptions, argumentCache); } // Include the options from a possibly specified configuration file, but don't overwrite existing settings. if (storm::settings::getModule<storm::settings::modules::GeneralSettings>().isConfigSet()) { this->setFromConfigurationFile(storm::settings::getModule<storm::settings::modules::GeneralSettings>().getConfigFilename()); } // Finally, check whether all modules are okay with the current settings. this->finalizeAllModules(); } void SettingsManager::setFromConfigurationFile(std::string const& configFilename) { std::map<std::string, std::vector<std::string>> configurationFileSettings = parseConfigFile(configFilename); for (auto const& optionArgumentsPair : configurationFileSettings) { auto options = this->longNameToOptions.find(optionArgumentsPair.first); // We don't need to check whether this option exists or not, because this is already checked when // parsing the configuration file. // Now go through all the matching options and set them according to the values. for (auto option : options->second) { if (option->getHasOptionBeenSet()) { // If the option was already set from the command line, we issue a warning and ignore the // settings from the configuration file. STORM_LOG_WARN("The option '" << option->getLongName() << "' of module '" << option->getModuleName() << "' has been set in the configuration file '" << configFilename << "', but was overwritten on the command line." << std::endl); } else { // If, however, the option has not been set yet, we try to assign values ot its arguments // based on the argument strings. setOptionArguments(optionArgumentsPair.first, option, optionArgumentsPair.second); } } } // Finally, check whether all modules are okay with the current settings. this->finalizeAllModules(); } void SettingsManager::printHelp(std::string const& hint) const { STORM_PRINT("usage: " << executableName << " [options]" << std::endl << std::endl); if (hint == "all") { // Find longest option name. uint_fast64_t maxLength = getPrintLengthOfLongestOption(); for (auto const& moduleName : this->moduleNames) { // Only print for visible modules. if (hasModule(moduleName, true)) { printHelpForModule(moduleName, maxLength); }; } } else { // Create a regular expression from the input hint. std::regex hintRegex(hint, std::regex_constants::ECMAScript | std::regex_constants::icase); // Remember which options we printed, so we don't display options twice. std::set<std::shared_ptr<Option>> printedOptions; // Try to match the regular expression against the known modules. std::vector<std::string> matchingModuleNames; uint_fast64_t maxLengthModules = 0; for (auto const& moduleName : this->moduleNames) { if (std::regex_search(moduleName, hintRegex)) { if (hasModule(moduleName, true)) { // Only consider visible modules. matchingModuleNames.push_back(moduleName); maxLengthModules = std::max(maxLengthModules, getPrintLengthOfLongestOption(moduleName)); // Add all options of this module to the list of printed options so we don't print them twice. auto optionIterator = this->moduleOptions.find(moduleName); printedOptions.insert(optionIterator->second.begin(), optionIterator->second.end()); } } } // Try to match the regular expression against the known options. std::vector<std::shared_ptr<Option>> matchingOptions; uint_fast64_t maxLengthOptions = 0; for (auto const& optionName : this->longOptionNames) { if (std::regex_search(optionName, hintRegex)) { auto optionIterator = this->longNameToOptions.find(optionName); for (auto const& option : optionIterator->second) { // Only add the option if we have not already added it to the list of options that is going // to be printed anyway. if (printedOptions.find(option) == printedOptions.end()) { maxLengthOptions = std::max(maxLengthOptions, option->getPrintLength()); matchingOptions.push_back(option); } } } } // Print the matching modules. uint_fast64_t maxLength = std::max(maxLengthModules, maxLengthOptions); if (matchingModuleNames.size() > 0) { STORM_PRINT("Matching modules for hint '" << hint << "':" << std::endl) for (auto const& matchingModuleName : matchingModuleNames) { printHelpForModule(matchingModuleName, maxLength); } } // Print the matching options. if (matchingOptions.size() > 0) { STORM_PRINT("Matching options for hint '" << hint << "':" << std::endl); for (auto const& option : matchingOptions) { STORM_PRINT(std::setw(maxLength) << std::left << *option << std::endl); } } if (matchingModuleNames.empty() && matchingOptions.empty()) { STORM_PRINT("Hint '" << hint << "' did not match any modules or options." << std::endl); } } } void SettingsManager::printHelpForModule(std::string const& moduleName, uint_fast64_t maxLength) const { auto moduleIterator = moduleOptions.find(moduleName); STORM_LOG_THROW(moduleIterator != moduleOptions.end(), storm::exceptions::IllegalFunctionCallException, "Cannot print help for unknown module '" << moduleName << "'."); STORM_PRINT("##### Module '" << moduleName << "' " << std::string(std::min(maxLength, maxLength - moduleName.length() - 16), '#') << std::endl); // Save the flags for std::cout so we can manipulate them and be sure they will be restored as soon as this // stream goes out of scope. boost::io::ios_flags_saver out(std::cout); for (auto const& option : moduleIterator->second) { STORM_PRINT(std::setw(maxLength) << std::left << *option << std::endl); } STORM_PRINT(std::endl); } uint_fast64_t SettingsManager::getPrintLengthOfLongestOption() const { uint_fast64_t length = 0; for (auto const& moduleName : this->moduleNames) { length = std::max(getPrintLengthOfLongestOption(moduleName), length); } return length; } uint_fast64_t SettingsManager::getPrintLengthOfLongestOption(std::string const& moduleName) const { auto moduleIterator = modules.find(moduleName); STORM_LOG_THROW(moduleIterator != modules.end(), storm::exceptions::IllegalFunctionCallException, "Unable to retrieve option length of unknown module '" << moduleName << "'."); return moduleIterator->second->getPrintLengthOfLongestOption(); } void SettingsManager::addModule(std::unique_ptr<modules::ModuleSettings>&& moduleSettings, bool doRegister) { auto moduleIterator = this->modules.find(moduleSettings->getModuleName()); STORM_LOG_THROW(moduleIterator == this->modules.end(), storm::exceptions::IllegalFunctionCallException, "Unable to register module '" << moduleSettings->getModuleName() << "' because a module with the same name already exists."); // Take over the module settings object. std::string const& moduleName = moduleSettings->getModuleName(); this->moduleNames.push_back(moduleName); this->modules.emplace(moduleSettings->getModuleName(), std::move(moduleSettings)); auto iterator = this->modules.find(moduleName); std::unique_ptr<modules::ModuleSettings> const& settings = iterator->second; if (doRegister) { this->moduleOptions.emplace(moduleName, std::vector<std::shared_ptr<Option>>()); // Now register the options of the module. for (auto const& option : settings->getOptions()) { this->addOption(option); } } } void SettingsManager::addOption(std::shared_ptr<Option> const& option) { // First, we register to which module the given option belongs. auto moduleOptionIterator = this->moduleOptions.find(option->getModuleName()); STORM_LOG_THROW(moduleOptionIterator != this->moduleOptions.end(), storm::exceptions::IllegalFunctionCallException, "Cannot add option for unknown module '" << option->getModuleName() << "'."); moduleOptionIterator->second.emplace_back(option); // Then, we add the option's name (and possibly short name) to the registered options. If a module prefix is // not required for this option, we have to add both versions to our mappings, the prefixed one and the // non-prefixed one. if (!option->getRequiresModulePrefix()) { bool isCompatible = this->isCompatible(option, option->getLongName(), this->longNameToOptions); STORM_LOG_THROW(isCompatible, storm::exceptions::IllegalFunctionCallException, "Unable to add option '" << option->getLongName() << "', because an option with the same name is incompatible with it."); addOptionToMap(option->getLongName(), option, this->longNameToOptions); } // For the prefixed name, we don't need a compatibility check, because a module is not allowed to register the same option twice. addOptionToMap(option->getModuleName() + ":" + option->getLongName(), option, this->longNameToOptions); longOptionNames.push_back(option->getModuleName() + ":" + option->getLongName()); if (option->getHasShortName()) { if (!option->getRequiresModulePrefix()) { bool isCompatible = this->isCompatible(option, option->getShortName(), this->shortNameToOptions); STORM_LOG_THROW(isCompatible, storm::exceptions::IllegalFunctionCallException, "Unable to add option '" << option->getLongName() << "', because an option with the same name is incompatible with it."); addOptionToMap(option->getShortName(), option, this->shortNameToOptions); } addOptionToMap(option->getModuleName() + ":" + option->getShortName(), option, this->shortNameToOptions); } } bool SettingsManager::hasModule(std::string const& moduleName, bool checkHidden) const { if (checkHidden) { return this->moduleOptions.find(moduleName) != this->moduleOptions.end(); } else { return this->modules.find(moduleName) != this->modules.end(); } } modules::ModuleSettings const& SettingsManager::getModule(std::string const& moduleName) const { auto moduleIterator = this->modules.find(moduleName); STORM_LOG_THROW(moduleIterator != this->modules.end(), storm::exceptions::IllegalFunctionCallException, "Cannot retrieve unknown module '" << moduleName << "'."); return *moduleIterator->second; } modules::ModuleSettings& SettingsManager::getModule(std::string const& moduleName) { auto moduleIterator = this->modules.find(moduleName); STORM_LOG_THROW(moduleIterator != this->modules.end(), storm::exceptions::IllegalFunctionCallException, "Cannot retrieve unknown module '" << moduleName << "'."); return *moduleIterator->second; } bool SettingsManager::isCompatible(std::shared_ptr<Option> const& option, std::string const& optionName, std::unordered_map<std::string, std::vector<std::shared_ptr<Option>>> const& optionMap) { auto optionIterator = optionMap.find(optionName); if (optionIterator != optionMap.end()) { for (auto const& otherOption : optionIterator->second) { bool locallyCompatible = option->isCompatibleWith(*otherOption); if (!locallyCompatible) { return false; } } } return true; } void SettingsManager::setOptionArguments(std::string const& optionName, std::shared_ptr<Option> option, std::vector<std::string> const& argumentCache) { STORM_LOG_THROW(argumentCache.size() <= option->getArgumentCount(), storm::exceptions::OptionParserException, "Too many arguments for option '" << optionName << "'."); STORM_LOG_THROW(!option->getHasOptionBeenSet(), storm::exceptions::OptionParserException, "Option '" << optionName << "' is set multiple times."); // Now set the provided argument values one by one. for (uint_fast64_t i = 0; i < argumentCache.size(); ++i) { ArgumentBase& argument = option->getArgument(i); bool conversionOk = argument.setFromStringValue(argumentCache[i]); STORM_LOG_THROW(conversionOk, storm::exceptions::OptionParserException, "Value '" << argumentCache[i] << "' is invalid for argument '" << argument.getName() << "' of option " << option->getModuleName() << ":" << option->getLongName() << "."); } // In case there are optional arguments that were not set, we set them to their default value. for (uint_fast64_t i = argumentCache.size(); i < option->getArgumentCount(); ++i) { ArgumentBase& argument = option->getArgument(i); argument.setFromDefaultValue(); } option->setHasOptionBeenSet(); } void SettingsManager::setOptionsArguments(std::string const& optionName, std::unordered_map<std::string, std::vector<std::shared_ptr<Option>>> const& optionMap, std::vector<std::string> const& argumentCache) { auto optionIterator = optionMap.find(optionName); STORM_LOG_THROW(optionIterator != optionMap.end(), storm::exceptions::OptionParserException, "Unknown option '" << optionName << "'."); // Iterate over all options and set the arguments. for (auto& option : optionIterator->second) { setOptionArguments(optionName, option, argumentCache); } } void SettingsManager::addOptionToMap(std::string const& name, std::shared_ptr<Option> const& option, std::unordered_map<std::string, std::vector<std::shared_ptr<Option>>>& optionMap) { auto optionIterator = optionMap.find(name); if (optionIterator == optionMap.end()) { std::vector<std::shared_ptr<Option>> optionVector; optionVector.push_back(option); optionMap.emplace(name, optionVector); } else { optionIterator->second.push_back(option); } } void SettingsManager::finalizeAllModules() { for (auto const& nameModulePair : this->modules) { nameModulePair.second->finalize(); nameModulePair.second->check(); } } std::map<std::string, std::vector<std::string>> SettingsManager::parseConfigFile(std::string const& filename) const { std::map<std::string, std::vector<std::string>> result; std::ifstream input; storm::utility::openFile(filename, input); bool globalScope = true; std::string activeModule = ""; uint_fast64_t lineNumber = 1; for (std::string line; getline(input, line); ++lineNumber) { // If the first character of the line is a "[", we expect the settings of a new module to start and // the line to be of the shape [<module>]. if (line.at(0) == '[') { STORM_LOG_THROW(line.at(0) == '[' && line.find("]") == line.length() - 1 && line.find("[", 1) == line.npos, storm::exceptions::OptionParserException, "Illegal module name header in configuration file '" << filename << " in line " << std::to_string(lineNumber) << ". Expected [<module>] where <module> is a placeholder for a known module."); // Extract the module name and check whether it's a legal one. std::string moduleName = line.substr(1, line.length() - 2); STORM_LOG_THROW(moduleName != "" && (moduleName == "global" || (this->modules.find(moduleName) != this->modules.end())), storm::exceptions::OptionParserException, "Module header in configuration file '" << filename << " in line " << std::to_string(lineNumber) << " refers to unknown module '" << moduleName << "."); // If the module name is "global", we unset the currently active module and treat all options to follow as unprefixed. if (moduleName == "global") { globalScope = true; } else { activeModule = moduleName; globalScope = false; } } else { // In this case, we expect the line to be of the shape o or o=a b c, where o is an option and a, b // and c are the values that are supposed to be assigned to the arguments of the option. std::size_t assignmentSignIndex = line.find("="); bool containsAssignment = false; if (assignmentSignIndex != line.npos) { containsAssignment = true; } std::string optionName; if (containsAssignment) { optionName = line.substr(0, assignmentSignIndex); } else { optionName = line; } if (globalScope) { STORM_LOG_THROW(this->longNameToOptions.find(optionName) != this->longNameToOptions.end(), storm::exceptions::OptionParserException, "Option assignment in configuration file '" << filename << " in line " << lineNumber << " refers to unknown option '" << optionName << "'."); } else { STORM_LOG_THROW(this->longNameToOptions.find(activeModule + ":" + optionName) != this->longNameToOptions.end(), storm::exceptions::OptionParserException, "Option assignment in configuration file '" << filename << " in line " << lineNumber << " refers to unknown option '" << activeModule << ":" << optionName << "'."); } std::string fullOptionName = (!globalScope ? activeModule + ":" : "") + optionName; STORM_LOG_WARN_COND(result.find(fullOptionName) == result.end(), "Option '" << fullOptionName << "' is set in line " << lineNumber << " of configuration file " << filename << ", but has been set before."); // If the current line is an assignment, split the right-hand side of the assignment into parts // enclosed by quotation marks. if (containsAssignment) { std::string assignedValues = line.substr(assignmentSignIndex + 1); std::vector<std::string> argumentCache; // As horrible as it may look, this regular expression matches either a quoted string (possibly // containing escaped quotes) or a simple word (without whitespaces and quotes). std::regex argumentRegex("\"(([^\\\\\"]|((\\\\\\\\)*\\\\\")|\\\\[^\"])*)\"|(([^ \\\\\"]|((\\\\\\\\)*\\\\\")|\\\\[^\"])+)"); boost::algorithm::trim_left(assignedValues); while (!assignedValues.empty()) { std::smatch match; bool hasMatch = std::regex_search(assignedValues, match, argumentRegex); // If the input could not be matched, we have a parsing error. STORM_LOG_THROW(hasMatch, storm::exceptions::OptionParserException, "Parsing error in configuration file '" << filename << "' in line " << lineNumber << ". Unexpected input '" << assignedValues << "'."); // Extract the matched argument and cut off the quotation marks if necessary. std::string matchedArgument = std::string(match[0].first, match[0].second); if (matchedArgument.at(0) == '"') { matchedArgument = matchedArgument.substr(1, matchedArgument.length() - 2); } argumentCache.push_back(matchedArgument); assignedValues = assignedValues.substr(match.length()); boost::algorithm::trim_left(assignedValues); } // After successfully parsing the argument values, we store them in the result map. result.emplace(fullOptionName, argumentCache); } else { // In this case, we can just insert the option to indicate it should be set (without arguments). result.emplace(fullOptionName, std::vector<std::string>()); } } } storm::utility::closeFile(input); return result; } SettingsManager const& manager() { return SettingsManager::manager(); } SettingsManager& mutableManager() { return SettingsManager::manager(); } storm::settings::modules::CoreSettings& mutableCoreSettings() { return dynamic_cast<storm::settings::modules::CoreSettings&>(mutableManager().getModule(storm::settings::modules::CoreSettings::moduleName)); } storm::settings::modules::IOSettings& mutableIOSettings() { return dynamic_cast<storm::settings::modules::IOSettings&>(mutableManager().getModule(storm::settings::modules::IOSettings::moduleName)); } storm::settings::modules::AbstractionSettings& mutableAbstractionSettings() { return dynamic_cast<storm::settings::modules::AbstractionSettings&>(mutableManager().getModule(storm::settings::modules::AbstractionSettings::moduleName)); } void initializeAll(std::string const& name, std::string const& executableName) { storm::settings::mutableManager().setName(name, executableName); // Register all known settings modules. storm::settings::addModule<storm::settings::modules::GeneralSettings>(); storm::settings::addModule<storm::settings::modules::IOSettings>(); storm::settings::addModule<storm::settings::modules::BuildSettings>(); storm::settings::addModule<storm::settings::modules::CoreSettings>(); storm::settings::addModule<storm::settings::modules::ModelCheckerSettings>(); storm::settings::addModule<storm::settings::modules::DebugSettings>(); storm::settings::addModule<storm::settings::modules::CuddSettings>(); storm::settings::addModule<storm::settings::modules::SylvanSettings>(); storm::settings::addModule<storm::settings::modules::GmmxxEquationSolverSettings>(); storm::settings::addModule<storm::settings::modules::EigenEquationSolverSettings>(); storm::settings::addModule<storm::settings::modules::NativeEquationSolverSettings>(); storm::settings::addModule<storm::settings::modules::EliminationSettings>(); storm::settings::addModule<storm::settings::modules::MinMaxEquationSolverSettings>(); storm::settings::addModule<storm::settings::modules::GameSolverSettings>(); storm::settings::addModule<storm::settings::modules::BisimulationSettings>(); storm::settings::addModule<storm::settings::modules::GlpkSettings>(); storm::settings::addModule<storm::settings::modules::GurobiSettings>(); storm::settings::addModule<storm::settings::modules::TopologicalEquationSolverSettings>(); storm::settings::addModule<storm::settings::modules::Smt2SmtSolverSettings>(); storm::settings::addModule<storm::settings::modules::ExplorationSettings>(); storm::settings::addModule<storm::settings::modules::ResourceSettings>(); storm::settings::addModule<storm::settings::modules::AbstractionSettings>(); storm::settings::addModule<storm::settings::modules::JaniExportSettings>(); storm::settings::addModule<storm::settings::modules::JitBuilderSettings>(); storm::settings::addModule<storm::settings::modules::MultiObjectiveSettings>(); storm::settings::addModule<storm::settings::modules::MultiplierSettings>(); } } }