/* * ORXONOX - the hottest 3D action shooter ever to exist * > www.orxonox.net < * * * License notice: * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * Author: * Fabian 'x3n' Landau * Co-authors: * ... * */ /** @file @brief Implementation of CommandEvaluation */ #include "CommandEvaluation.h" #include "util/StringUtils.h" #include "CommandExecutor.h" #include "ConsoleCommand.h" #include "ConsoleCommandManager.h" namespace orxonox { /** @brief Constructor: Initializes the command evaluation with an empty command. */ CommandEvaluation::CommandEvaluation() { this->initialize(""); } /** @brief Initializes all values. */ void CommandEvaluation::initialize(const std::string& command) { this->execCommand_ = nullptr; this->hintCommand_ = nullptr; this->string_ = command; this->execArgumentsOffset_ = 0; this->hintArgumentsOffset_ = 0; this->bPossibleArgumentsRetrieved_ = false; this->possibleArguments_.clear(); this->bEvaluatedArguments_ = false; this->bTriedToEvaluatedArguments_ = false; this->numberOfEvaluatedArguments_ = 0; // split the command into tokens this->tokens_.split(command, " ", SubString::WhiteSpaces, false, '\\', true, '"', true, '{', '}', true, '\0'); } /** @brief Returns the number of tokens according to the definition of CommandExecutor (which counts also an empty argument at the end of the string). */ unsigned int CommandEvaluation::getNumberOfArguments() const { unsigned int count = this->tokens_.size(); // If the last char in the string is a space character (or the string is empty), add +1 to the number of tokens, because this counts as an additional (but empty) argument if (count == 0 || this->string_[this->string_.size() - 1] == ' ') return count + 1; else return count; } /** @brief Returns the last argument (which is the one the user currently enters into the shell). */ const std::string& CommandEvaluation::getLastArgument() const { // the string is empty or ends with a space character, the user is just about to enter a new argument (but its still empty). return a blank string in this case. if (this->tokens_.size() == 0 || this->string_[this->string_.size() - 1] == ' ') return BLANKSTRING; else return this->tokens_.back(); } /** @brief Returns the token with the given index (or a blank string if it doesn't exist). */ const std::string& CommandEvaluation::getToken(unsigned int i) const { if (i < this->tokens_.size()) return this->tokens_[i]; else return BLANKSTRING; } /** @brief Executes the command which was evaluated by this object. @return Returns the error code (see @ref CommandExecutorErrorCodes "CommandExecutor error codes") */ int CommandEvaluation::execute() { int error; this->query(&error); return error; } /** @brief Executes the command which was evaluated by this object and returns its return-value. @param error A pointer to an integer (or nullptr) which will be used to write error codes to (see @ref CommandExecutorErrorCodes "CommandExecutor error codes") @return Returns the result of the command (or MultiType::Null if there is no return value) */ MultiType CommandEvaluation::query(int* error) { // check if an error value was passed by reference if (error) { // Determine the error-code and return if it is not Success *error = CommandExecutor::Success; if (!this->execCommand_) *error = CommandExecutor::Inexistent; else if (!this->execCommand_->isActive()) *error = CommandExecutor::Deactivated; else if (!this->execCommand_->hasAccess()) *error = CommandExecutor::Denied; if (*error != CommandExecutor::Success) return MultiType::Null; } // check if it's possible to execute the command if (this->execCommand_ && this->execCommand_->isActive() && this->execCommand_->hasAccess()) { // if the arguments weren't evaluated yet, do it now. if (!this->bTriedToEvaluatedArguments_) this->evaluateArguments(false); // check if the argument evaluation succeded if (this->bEvaluatedArguments_) { orxout(verbose, context::commands) << "CE_execute (evaluation): " << this->execCommand_->getName() << " with " << this->numberOfEvaluatedArguments_ << " arguments: " << this->arguments_[0] << ' ' << this->arguments_[1] << ' ' << this->arguments_[2] << ' ' << this->arguments_[3] << ' ' << this->arguments_[4] << endl; // pass as many arguments to the executor as were evaluated (thus the executor can still use additional default values) switch (this->numberOfEvaluatedArguments_) { case 0: return (*this->execCommand_->getExecutor())(); case 1: return (*this->execCommand_->getExecutor())(this->arguments_[0]); case 2: return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1]); case 3: return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1], this->arguments_[2]); case 4: return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1], this->arguments_[2], this->arguments_[3]); case 5: default: return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1], this->arguments_[2], this->arguments_[3], this->arguments_[4]); } } else if (error) *error = CommandExecutor::Incomplete; } // return a null value in case of an error return MultiType::Null; } /** @brief Evaluates the arguments of the command. @param bPrintError If true, the function prints an error message if it doesn't succeed @return Returns the error code (see @ref CommandExecutorErrorCodes "CommandExecutor error codes") */ int CommandEvaluation::evaluateArguments(bool bPrintError) { this->bTriedToEvaluatedArguments_ = true; // check if there's a command to execute if (!this->execCommand_) { if (bPrintError) orxout(internal_error, context::commands) << "Can't evaluate arguments, no console command assigned." << endl; return CommandExecutor::Inexistent; } int error; // try to evaluate the arguments using the executor of the evaluated command. // the arguments are currently stored as strings in token_, but afterwards they will be converted to the right type and stored in arguments_ this->numberOfEvaluatedArguments_ = this->execCommand_->getExecutor()->evaluateArguments(this->tokens_.subSet(this->execArgumentsOffset_), this->arguments_, &error, " "); // check if an error occurred if (!error) this->bEvaluatedArguments_ = true; else if (bPrintError) orxout(internal_error, context::commands) << "Can't evaluate arguments, not enough arguments given." << endl; return error; } /** @brief Replaces an evaluated argument with a new value. @param index The index of the parameter (the first argument has index 0) @param arg The new value of the parameter */ void CommandEvaluation::setEvaluatedArgument(unsigned int index, const MultiType& arg) { if (index < MAX_FUNCTOR_ARGUMENTS) this->arguments_[index] = arg; } /** @brief Returns the evaluated argument with given index. @param index The index of the argument (the first argument has index 0) */ MultiType CommandEvaluation::getEvaluatedArgument(unsigned int index) const { if (index < MAX_FUNCTOR_ARGUMENTS) return this->arguments_[index]; return MultiType::Null; } /** @brief Completes the given command string using the list of possible arguments. @return Returns the completed command string This is called by the shell if the user presses the @a tab key. The currently entered argument will be completed as good as possible by using the argument completion list of the evaluated command. */ std::string CommandEvaluation::complete() { // check if it's possible to complete the command if (!this->hintCommand_ || !this->hintCommand_->isActive()) return this->string_; // get the list of possible arguments if (!this->bPossibleArgumentsRetrieved_) this->retrievePossibleArguments(); // if the list is empty, return the current command string if (CommandEvaluation::getSize(this->possibleArguments_) == 0) { return this->string_; } else { // get the first part of the command string from the beginning up to the last space character std::string output = this->string_.substr(0, this->string_.find_last_of(' ') + 1); // add the common begin of all possible arguments output += CommandEvaluation::getCommonBegin(this->possibleArguments_); // return the resulting string return output; } } /** @brief Returns a string containing hints or possible arguments for the evaluated command. This is called by the shell if the user presses the @a tab key. It prints a list of possible arguments or other hints, returned by the argument completion list of the evaluated command. If there's no such list, the syntax of the command is returned. */ std::string CommandEvaluation::hint() { // check if it's possible to get hints for this command if (!this->hintCommand_ || !this->hintCommand_->isActive()) return ""; // get the list of possible arguments if (!this->bPossibleArgumentsRetrieved_) this->retrievePossibleArguments(); // return the list of possible arguments if: // a) it contains at least one non-empty argument // b) it contains an entry that may be empty (not an actual argument, just a helping text) AND the command is valid if (CommandEvaluation::getSize(this->possibleArguments_) > 0 || (!this->possibleArguments_.empty() && this->isValid())) return CommandEvaluation::dump(this->possibleArguments_); // at this point there's no valid argument in the list, so check if the command is actually valid if (this->isValid()) { // yes it is - return the syntax of the command return CommandEvaluation::dump(this->hintCommand_); } else { // no the command is not valid if (this->getNumberOfArguments() > 2) { // the user typed 2+ arguments, but they don't name a command - print an error return std::string("Error: There is no command with name \"") + this->getToken(0) + " " + this->getToken(1) + "\"."; } else { // the user typed 1-2 arguments, check what he tried to type and print a suitable error std::string groupLC = getLowercase(this->getToken(0)); for (const auto& mapEntry : ConsoleCommandManager::getInstance().getCommandsLC()) if (mapEntry.first == groupLC) return std::string("Error: There is no command in group \"") + this->getToken(0) + "\" starting with \"" + this->getToken(1) + "\"."; return std::string("Error: There is no command starting with \"") + this->getToken(0) + "\"."; } } } /** @brief If the command couln't be evaluated because it doesn't exist, print a suggestion for a command that looks close to the entered command (useful if the user mistyped the command). */ std::string CommandEvaluation::getCommandSuggestion() const { std::string token0_LC = getLowercase(this->getToken(0)); std::string token1_LC = getLowercase(this->getToken(1)); std::string nearestCommand; unsigned int nearestDistance = (unsigned int)-1; // iterate through all groups and their commands and calculate the distance to the current command. keep the best. for (const auto& mapEntryGroup : ConsoleCommandManager::getInstance().getCommandsLC()) { if (mapEntryGroup.first != "") { for (const auto& mapEntryName : mapEntryGroup.second) { std::string command = mapEntryGroup.first + " " + mapEntryName.first; unsigned int distance = getLevenshteinDistance(command, token0_LC + " " + token1_LC); if (distance < nearestDistance) { nearestCommand = command; nearestDistance = distance; } } } } // now also iterate through all shortcuts and keep the best if it's better than the one found above. std::map>::const_iterator it_group = ConsoleCommandManager::getInstance().getCommandsLC().find(""); if (it_group != ConsoleCommandManager::getInstance().getCommandsLC().end()) { for (const auto& mapEntry : it_group->second) { std::string command = mapEntry.first; unsigned int distance = getLevenshteinDistance(command, token0_LC); if (distance < nearestDistance) { nearestCommand = command; nearestDistance = distance; } } } // return the command that's closest to the current one. return nearestCommand; } /** @brief Gets the possible arguments for the command in its current state. */ void CommandEvaluation::retrievePossibleArguments() { this->bPossibleArgumentsRetrieved_ = true; // we use the hintCommand_ to get possible arguments. get the index of the last argument. limit the index if its greater than the number of arguments supported by the command. unsigned int argumentID = std::min(this->getNumberOfArguments() - this->hintArgumentsOffset_, this->hintCommand_->getExecutor()->getParamCount()); // get the argument completer for the given argument index ArgumentCompleter* ac = this->hintCommand_->getArgumentCompleter(argumentID - 1); // check if an argument completer exists if (ac) { MultiType arg[MAX_FUNCTOR_ARGUMENTS]; // the index of the last argument in the command string that is supported by this argument completer size_t max = this->hintArgumentsOffset_ + this->hintCommand_->getExecutor()->getParamCount(); // write the argument strings to the argument array (in reversed order, as required by the argument completion function) for (size_t i = 0; i < argumentID; ++i) arg[i] = this->getToken(std::min(this->getNumberOfArguments(), (unsigned int)max) - i - 1); // check if there are more arguments given by the user than supported if (this->getNumberOfArguments() > max) { // yes - now check if multiple words are supported by the argument completer if (ac->useMultipleWords()) { // yes - join the surplus arguments std::string surplusArguments = this->tokens_.subSet(max - 1).join(); if (this->string_[this->string_.size() - 1] == ' ') surplusArguments += ' '; // pass all surplus arguments as the first argument to the argument completer this->possibleArguments_ = (*ac)(surplusArguments, arg[1], arg[2], arg[3], arg[4]); // strip the list using the last argument CommandEvaluation::strip(this->possibleArguments_, this->getToken(this->getNumberOfArguments() - 1)); } else { // no - the user typed more arguments than supported, no action } } else { // no - so simply call the argument completer and get the list of arguments this->possibleArguments_ = (*ac)(arg[0], arg[1], arg[2], arg[3], arg[4]); // strip the list using the last argument (stored arg[0]) CommandEvaluation::strip(this->possibleArguments_, arg[0]); } } } /** @brief Returns the size of an argument completion list - empty ("") arguments are not counted. */ /* static */ size_t CommandEvaluation::getSize(const ArgumentCompletionList& list) { size_t count = 0; for (const ArgumentCompletionListElement& element : list) if (element.getComparable() != "") ++count; return count; } /** @brief Removes all elements from the list that don't start with @a fragment. @param list The argument completion list @param fragment The argument that is currently entered by the user and that needs to be completed */ /* static */ void CommandEvaluation::strip(ArgumentCompletionList& list, const std::string& fragment) { std::string fragmentLC = getLowercase(fragment); // iterate through the list for (ArgumentCompletionList::iterator it = list.begin(); it != list.end(); ) { const std::string& entry = it->getComparable(); // check if the argument is empty - if yes, keep it always in the list if (entry == "") { ++it; continue; } // check the length of the argument - arguments smaller than 'fragment' are always erased if (entry.size() < fragmentLC.size()) { list.erase(it++); } else { // compare the argument char by char with 'fragment' bool bErase = false; for (size_t i = 0; i < fragmentLC.size(); ++i) { if (fragmentLC[i] != entry[i]) { bErase = true; break; } } if (bErase) list.erase(it++); else ++it; } } } /** @brief Returns the commond begin of all arguments in the list. */ /* static */ std::string CommandEvaluation::getCommonBegin(const ArgumentCompletionList& list) { if (CommandEvaluation::getSize(list) == 0) { // no (non-empty) values in the list, return an empty string return ""; } else if (CommandEvaluation::getSize(list) == 1) { // only one (non-empty) value in the list - search it and return it for (const ArgumentCompletionListElement& element : list) { if (element.getComparable() != "") { // arguments that have a separate string to be displayed need a little more care - just return them without modification. add a space character to the others. if (element.hasDisplay()) return (element.getString()); else return (element.getString() + ' '); } } return ""; } else { // multiple arguments in the list - iterate through it and find the common begin of all arguments std::string output; for (unsigned int i = 0; true; i++) { char tempComparable = '\0'; char temp = '\0'; for (const ArgumentCompletionListElement& element : list) { const std::string& argumentComparable = element.getComparable(); const std::string& argument = element.getString(); // ignore empty arguments if (argumentComparable == "") continue; if (argument.size() > i) { if (tempComparable == '\0') { // the first entry is always taken tempComparable = argumentComparable[i]; temp = argument[i]; } else { // all other entries need comparison to the first entry if (tempComparable != argumentComparable[i]) return output; else if (temp != argument[i]) // the comparables match, but the normal chars don't - switch to comparable only temp = tempComparable; } } else { return output; } } output += temp; } return output; } } /** @brief Joins the elements of the given list to a string. */ /* static */ std::string CommandEvaluation::dump(const ArgumentCompletionList& list) { std::string output; for (const ArgumentCompletionListElement& element : list) { output += element.getDisplay(); // add a space character between two elements for all non-empty arguments if (element.getComparable() != "") output += ' '; } return output; } /** @brief Returns a string that explains the syntax of the given command. */ /* static */ std::string CommandEvaluation::dump(const ConsoleCommand* command) { // get the name of the command std::string output = command->getName(); // check if there are parameters if (command->getExecutor()->getParamCount() > 0) output += ": "; // iterate through the parameters for (unsigned int i = 0; i < command->getExecutor()->getParamCount(); i++) { // separate the parameters with a space character if (i != 0) output += ' '; // print default values in [], others in {} braces if (command->getExecutor()->defaultValueSet(i)) output += '['; else output += '{'; // add the type-name of the parameter output += command->getExecutor()->getTypenameParam(i); // print the default value if available if (command->getExecutor()->defaultValueSet(i)) output += '=' + command->getExecutor()->getDefaultValue(i).get() + ']'; else output += '}'; } return output; } }