Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/cpp11_v2/src/libraries/core/command/CommandEvaluation.cc @ 10765

Last change on this file since 10765 was 10765, checked in by landauf, 9 years ago

replace 'NULL' by 'nullptr'

  • Property svn:eol-style set to native
File size: 24.7 KB
RevLine 
[1505]1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Fabian 'x3n' Landau
24 *   Co-authors:
25 *      ...
26 *
27 */
28
[7401]29/**
30    @file
31    @brief Implementation of CommandEvaluation
32*/
33
[1505]34#include "CommandEvaluation.h"
[3196]35
[3280]36#include "util/StringUtils.h"
[7228]37#include "CommandExecutor.h"
[1505]38#include "ConsoleCommand.h"
[10624]39#include "ConsoleCommandManager.h"
[1505]40
41namespace orxonox
42{
[7401]43    /**
44        @brief Constructor: Initializes the command evaluation with an empty command.
45    */
[1505]46    CommandEvaluation::CommandEvaluation()
47    {
48        this->initialize("");
49    }
50
[7401]51    /**
52        @brief Initializes all values.
53    */
[1505]54    void CommandEvaluation::initialize(const std::string& command)
55    {
[7228]56        this->execCommand_ = 0;
57        this->hintCommand_ = 0;
58        this->string_ = command;
59        this->execArgumentsOffset_ = 0;
60        this->hintArgumentsOffset_ = 0;
61        this->bPossibleArgumentsRetrieved_ = false;
62        this->possibleArguments_.clear();
[7401]63        this->bEvaluatedArguments_ = false;
64        this->bTriedToEvaluatedArguments_ = false;
65        this->numberOfEvaluatedArguments_ = 0;
[1505]66
[7401]67        // split the command into tokens
[7276]68        this->tokens_.split(command, " ", SubString::WhiteSpaces, false, '\\', true, '"', true, '{', '}', true, '\0');
[7228]69    }
[1505]70
[7401]71    /**
72        @brief Returns the number of tokens according to the definition of CommandExecutor (which counts also an empty argument at the end of the string).
73    */
[7228]74    unsigned int CommandEvaluation::getNumberOfArguments() const
75    {
76        unsigned int count = this->tokens_.size();
[7401]77
78        // 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
79        if (count == 0 || this->string_[this->string_.size() - 1] == ' ')
80            return count + 1;
81        else
[7228]82            return count;
83    }
[1505]84
[7401]85    /**
86        @brief Returns the last argument (which is the one the user currently enters into the shell).
87    */
[7228]88    const std::string& CommandEvaluation::getLastArgument() const
89    {
[7401]90        // 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.
91        if (this->tokens_.size() == 0 || this->string_[this->string_.size() - 1] == ' ')
92            return BLANKSTRING;
93        else
[7228]94            return this->tokens_.back();
95    }
[1505]96
[7401]97    /**
98        @brief Returns the token with the given index (or a blank string if it doesn't exist).
99    */
[7228]100    const std::string& CommandEvaluation::getToken(unsigned int i) const
101    {
102        if (i < this->tokens_.size())
103            return this->tokens_[i];
104        else
105            return BLANKSTRING;
[1505]106    }
107
[7401]108    /**
109        @brief Executes the command which was evaluated by this object.
110        @return Returns the error code (see @ref CommandExecutorErrorCodes "CommandExecutor error codes")
111    */
[7230]112    int CommandEvaluation::execute()
[1505]113    {
[7228]114        int error;
115        this->query(&error);
116        return error;
[7189]117    }
118
[7401]119    /**
120        @brief Executes the command which was evaluated by this object and returns its return-value.
[10765]121        @param error A pointer to an integer (or nullptr) which will be used to write error codes to (see @ref CommandExecutorErrorCodes "CommandExecutor error codes")
[9550]122        @return Returns the result of the command (or MultiType::Null if there is no return value)
[7401]123    */
[7230]124    MultiType CommandEvaluation::query(int* error)
[7189]125    {
[7401]126        // check if an error value was passed by reference
[7228]127        if (error)
[1505]128        {
[7401]129            // Determine the error-code and return if it is not Success
130
[7228]131            *error = CommandExecutor::Success;
[1505]132
[7228]133            if (!this->execCommand_)
[8858]134                *error = CommandExecutor::Inexistent;
[7228]135            else if (!this->execCommand_->isActive())
136                *error = CommandExecutor::Deactivated;
137            else if (!this->execCommand_->hasAccess())
138                *error = CommandExecutor::Denied;
[1505]139
[7228]140            if (*error != CommandExecutor::Success)
[9550]141                return MultiType::Null;
[1505]142        }
143
[7401]144        // check if it's possible to execute the command
[7228]145        if (this->execCommand_ && this->execCommand_->isActive() && this->execCommand_->hasAccess())
[7230]146        {
[7401]147            // if the arguments weren't evaluated yet, do it now.
148            if (!this->bTriedToEvaluatedArguments_)
149                this->evaluateArguments(false);
[7230]150
[7401]151            // check if the argument evaluation succeded
152            if (this->bEvaluatedArguments_)
[7230]153            {
[8858]154                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;
[7401]155
156                // pass as many arguments to the executor as were evaluated (thus the executor can still use additional default values)
157                switch (this->numberOfEvaluatedArguments_)
[7230]158                {
159                    case 0:  return (*this->execCommand_->getExecutor())();
[7401]160                    case 1:  return (*this->execCommand_->getExecutor())(this->arguments_[0]);
161                    case 2:  return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1]);
162                    case 3:  return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1], this->arguments_[2]);
163                    case 4:  return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1], this->arguments_[2], this->arguments_[3]);
[7230]164                    case 5:
[7401]165                    default: return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1], this->arguments_[2], this->arguments_[3], this->arguments_[4]);
[7230]166                }
167            }
[7238]168            else if (error)
169                *error = CommandExecutor::Incomplete;
[7230]170        }
[7238]171
[7401]172        // return a null value in case of an error
[9550]173        return MultiType::Null;
[1505]174    }
175
[7401]176    /**
177        @brief Evaluates the arguments of the command.
178        @param bPrintError If true, the function prints an error message if it doesn't succeed
179        @return Returns the error code (see @ref CommandExecutorErrorCodes "CommandExecutor error codes")
180    */
181    int CommandEvaluation::evaluateArguments(bool bPrintError)
[1505]182    {
[7401]183        this->bTriedToEvaluatedArguments_ = true;
[7230]184
[7401]185        // check if there's a command to execute
[7230]186        if (!this->execCommand_)
187        {
188            if (bPrintError)
[8858]189                orxout(internal_error, context::commands) << "Can't evaluate arguments, no console command assigned." << endl;
190            return CommandExecutor::Inexistent;
[7230]191        }
192
193        int error;
[7401]194
195        // try to evaluate the arguments using the executor of the evaluated command.
196        // the arguments are currently stored as strings in token_, but afterwards they will be converted to the right type and stored in arguments_
197        this->numberOfEvaluatedArguments_ = this->execCommand_->getExecutor()->evaluateArguments(this->tokens_.subSet(this->execArgumentsOffset_), this->arguments_, &error, " ");
198
199        // check if an error occurred
[7230]200        if (!error)
[7401]201            this->bEvaluatedArguments_ = true;
[7230]202        else if (bPrintError)
[8858]203            orxout(internal_error, context::commands) << "Can't evaluate arguments, not enough arguments given." << endl;
[7230]204
205        return error;
206    }
207
[7401]208    /**
209        @brief Replaces an evaluated argument with a new value.
210        @param index The index of the parameter (the first argument has index 0)
211        @param arg The new value of the parameter
212    */
213    void CommandEvaluation::setEvaluatedArgument(unsigned int index, const MultiType& arg)
[7230]214    {
215        if (index < MAX_FUNCTOR_ARGUMENTS)
[7401]216            this->arguments_[index] = arg;
[7230]217    }
218
[7401]219    /**
220        @brief Returns the evaluated argument with given index.
221        @param index The index of the argument (the first argument has index 0)
222    */
223    MultiType CommandEvaluation::getEvaluatedArgument(unsigned int index) const
[7230]224    {
225        if (index < MAX_FUNCTOR_ARGUMENTS)
[7401]226            return this->arguments_[index];
[7230]227
[9550]228        return MultiType::Null;
[7230]229    }
230
[7401]231    /**
232        @brief Completes the given command string using the list of possible arguments.
233        @return Returns the completed command string
234
235        This is called by the shell if the user presses the @a tab key. The currently entered
236        argument will be completed as good as possible by using the argument completion list
237        of the evaluated command.
238    */
[7230]239    std::string CommandEvaluation::complete()
240    {
[7401]241        // check if it's possible to complete the command
[7228]242        if (!this->hintCommand_ || !this->hintCommand_->isActive())
243            return this->string_;
[1505]244
[7401]245        // get the list of possible arguments
[7228]246        if (!this->bPossibleArgumentsRetrieved_)
247            this->retrievePossibleArguments();
[1505]248
[7401]249        // if the list is empty, return the current command string
[7233]250        if (CommandEvaluation::getSize(this->possibleArguments_) == 0)
[7228]251        {
252            return this->string_;
253        }
254        else
255        {
[7401]256            // get the first part of the command string from the beginning up to the last space character
[7230]257            std::string output = this->string_.substr(0, this->string_.find_last_of(' ') + 1);
[7401]258
259            // add the common begin of all possible arguments
[7228]260            output += CommandEvaluation::getCommonBegin(this->possibleArguments_);
[7401]261
262            // return the resulting string
[7228]263            return output;
[1505]264        }
265    }
266
[7401]267    /**
268        @brief Returns a string containing hints or possible arguments for the evaluated command.
269
270        This is called by the shell if the user presses the @a tab key. It prints a list of
271        possible arguments or other hints, returned by the argument completion list of the
272        evaluated command. If there's no such list, the syntax of the command is returned.
273    */
[7230]274    std::string CommandEvaluation::hint()
[1505]275    {
[7401]276        // check if it's possible to get hints for this command
[7228]277        if (!this->hintCommand_ || !this->hintCommand_->isActive())
278            return "";
[1505]279
[7401]280        // get the list of possible arguments
[7228]281        if (!this->bPossibleArgumentsRetrieved_)
282            this->retrievePossibleArguments();
[1505]283
[7401]284        // return the list of possible arguments if:
285        //   a) it contains at least one non-empty argument
286        //   b) it contains an entry that may be empty (not an actual argument, just a helping text) AND the command is valid
[7235]287        if (CommandEvaluation::getSize(this->possibleArguments_) > 0 || (!this->possibleArguments_.empty() && this->isValid()))
[7228]288            return CommandEvaluation::dump(this->possibleArguments_);
[1505]289
[7401]290        // at this point there's no valid argument in the list, so check if the command is actually valid
[7228]291        if (this->isValid())
[1505]292        {
[7401]293            // yes it is - return the syntax of the command
[7228]294            return CommandEvaluation::dump(this->hintCommand_);
[1505]295        }
[7228]296        else
[1505]297        {
[7401]298            // no the command is not valid
[7228]299            if (this->getNumberOfArguments() > 2)
300            {
[7401]301                // the user typed 2+ arguments, but they don't name a command - print an error
[7228]302                return std::string("Error: There is no command with name \"") + this->getToken(0) + " " + this->getToken(1) + "\".";
303            }
304            else
305            {
[7401]306                // the user typed 1-2 arguments, check what he tried to type and print a suitable error
[7228]307                std::string groupLC = getLowercase(this->getToken(0));
[10624]308                for (std::map<std::string, std::map<std::string, ConsoleCommand*> >::const_iterator it_group = ConsoleCommandManager::getInstance().getCommandsLC().begin(); it_group != ConsoleCommandManager::getInstance().getCommandsLC().end(); ++it_group)
[7238]309                    if (it_group->first == groupLC)
[7228]310                        return std::string("Error: There is no command in group \"") + this->getToken(0) + "\" starting with \"" + this->getToken(1) + "\".";
311
312                return std::string("Error: There is no command starting with \"") + this->getToken(0) + "\".";
313            }
[1505]314        }
315    }
316
[7401]317    /**
318        @brief If the command couln't be evaluated because it doesn't exist, print a suggestion for
319        a command that looks close to the entered command (useful if the user mistyped the command).
320    */
[7238]321    std::string CommandEvaluation::getCommandSuggestion() const
322    {
323        std::string token0_LC = getLowercase(this->getToken(0));
324        std::string token1_LC = getLowercase(this->getToken(1));
325
326        std::string nearestCommand;
327        unsigned int nearestDistance = (unsigned int)-1;
328
[7401]329        // iterate through all groups and their commands and calculate the distance to the current command. keep the best.
[10624]330        for (std::map<std::string, std::map<std::string, ConsoleCommand*> >::const_iterator it_group = ConsoleCommandManager::getInstance().getCommandsLC().begin(); it_group != ConsoleCommandManager::getInstance().getCommandsLC().end(); ++it_group)
[7238]331        {
332            if (it_group->first != "")
333            {
334                for (std::map<std::string, ConsoleCommand*>::const_iterator it_name = it_group->second.begin(); it_name != it_group->second.end(); ++it_name)
335                {
336                    std::string command = it_group->first + " " + it_name->first;
337                    unsigned int distance = getLevenshteinDistance(command, token0_LC + " " + token1_LC);
338                    if (distance < nearestDistance)
339                    {
340                        nearestCommand = command;
341                        nearestDistance = distance;
342                    }
343                }
344            }
345        }
346
[7401]347        // now also iterate through all shortcuts and keep the best if it's better than the one found above.
[10624]348        std::map<std::string, std::map<std::string, ConsoleCommand*> >::const_iterator it_group = ConsoleCommandManager::getInstance().getCommandsLC().find("");
349        if (it_group !=  ConsoleCommandManager::getInstance().getCommandsLC().end())
[7238]350        {
351            for (std::map<std::string, ConsoleCommand*>::const_iterator it_name = it_group->second.begin(); it_name != it_group->second.end(); ++it_name)
352            {
353                std::string command = it_name->first;
354                unsigned int distance = getLevenshteinDistance(command, token0_LC);
355                if (distance < nearestDistance)
356                {
357                    nearestCommand = command;
358                    nearestDistance = distance;
359                }
360            }
361        }
362
[7401]363        // return the command that's closest to the current one.
[7238]364        return nearestCommand;
365    }
366
[7401]367    /**
368        @brief Gets the possible arguments for the command in its current state.
369    */
[7230]370    void CommandEvaluation::retrievePossibleArguments()
[1505]371    {
[7228]372        this->bPossibleArgumentsRetrieved_ = true;
[7401]373
374        // 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.
[7228]375        unsigned int argumentID = std::min(this->getNumberOfArguments() - this->hintArgumentsOffset_, this->hintCommand_->getExecutor()->getParamCount());
[7401]376
377        // get the argument completer for the given argument index
[7228]378        ArgumentCompleter* ac = this->hintCommand_->getArgumentCompleter(argumentID - 1);
[1505]379
[7401]380        // check if an argument completer exists
[7228]381        if (ac)
382        {
[7401]383            MultiType arg[MAX_FUNCTOR_ARGUMENTS];
[1505]384
[7401]385            // the index of the last argument in the command string that is supported by this argument completer
[7233]386            size_t max = this->hintArgumentsOffset_ + this->hintCommand_->getExecutor()->getParamCount();
387
[7401]388            // write the argument strings to the argument array (in reversed order, as required by the argument completion function)
[7228]389            for (size_t i = 0; i < argumentID; ++i)
[7401]390                arg[i] = this->getToken(std::min(this->getNumberOfArguments(), (unsigned int)max) - i - 1);
[7228]391
[7401]392            // check if there are more arguments given by the user than supported
[7233]393            if (this->getNumberOfArguments() > max)
394            {
[7401]395                // yes - now check if multiple words are supported by the argument completer
[7233]396                if (ac->useMultipleWords())
397                {
[7401]398                    // yes - join the surplus arguments
[7233]399                    std::string surplusArguments = this->tokens_.subSet(max - 1).join();
400                    if (this->string_[this->string_.size() - 1] == ' ')
401                        surplusArguments += ' ';
[7228]402
[7401]403                    // pass all surplus arguments as the first argument to the argument completer
404                    this->possibleArguments_ = (*ac)(surplusArguments, arg[1], arg[2], arg[3], arg[4]);
405
406                    // strip the list using the last argument
[7233]407                    CommandEvaluation::strip(this->possibleArguments_, this->getToken(this->getNumberOfArguments() - 1));
408                }
[7401]409                else
410                {
411                    // no - the user typed more arguments than supported, no action
412                }
[7233]413            }
414            else
415            {
[7401]416                // no - so simply call the argument completer and get the list of arguments
417                this->possibleArguments_ = (*ac)(arg[0], arg[1], arg[2], arg[3], arg[4]);
418
419                // strip the list using the last argument (stored arg[0])
420                CommandEvaluation::strip(this->possibleArguments_, arg[0]);
[7233]421            }
[7228]422        }
[1505]423    }
424
[7401]425    /**
426        @brief Returns the size of an argument completion list - empty ("") arguments are not counted.
427    */
[7235]428    /* static */ size_t CommandEvaluation::getSize(const ArgumentCompletionList& list)
429    {
430        size_t count = 0;
431        for (ArgumentCompletionList::const_iterator it = list.begin(); it != list.end(); ++it)
432            if (it->getComparable() != "")
433                ++count;
434        return count;
435    }
436
[7401]437    /**
438        @brief Removes all elements from the list that don't start with @a fragment.
439        @param list The argument completion list
440        @param fragment The argument that is currently entered by the user and that needs to be completed
441    */
[7228]442    /* static */ void CommandEvaluation::strip(ArgumentCompletionList& list, const std::string& fragment)
[1505]443    {
[7228]444        std::string fragmentLC = getLowercase(fragment);
[1505]445
[7401]446        // iterate through the list
[7228]447        for (ArgumentCompletionList::iterator it = list.begin(); it != list.end(); )
[1505]448        {
[7228]449            const std::string& entry = it->getComparable();
[1505]450
[7401]451            // check if the argument is empty - if yes, keep it always in the list
[7233]452            if (entry == "")
453            {
454                ++it;
455                continue;
456            }
457
[7401]458            // check the length of the argument - arguments smaller than 'fragment' are always erased
[7228]459            if (entry.size() < fragmentLC.size())
460            {
461                list.erase(it++);
462            }
463            else
464            {
[7401]465                // compare the argument char by char with 'fragment'
[7228]466                bool bErase = false;
467                for (size_t i = 0; i < fragmentLC.size(); ++i)
468                {
469                    if (fragmentLC[i] != entry[i])
470                    {
471                        bErase = true;
472                        break;
473                    }
474                }
475
476                if (bErase)
477                    list.erase(it++);
478                else
479                    ++it;
480            }
[1505]481        }
482    }
483
[7401]484    /**
485        @brief Returns the commond begin of all arguments in the list.
486    */
[7228]487    /* static */ std::string CommandEvaluation::getCommonBegin(const ArgumentCompletionList& list)
488    {
[7233]489        if (CommandEvaluation::getSize(list) == 0)
[7228]490        {
[7401]491            // no (non-empty) values in the list, return an empty string
[7228]492            return "";
493        }
[7233]494        else if (CommandEvaluation::getSize(list) == 1)
[7228]495        {
[7401]496            // only one (non-empty) value in the list - search it and return it
[7233]497            for (ArgumentCompletionList::const_iterator it = list.begin(); it != list.end(); ++it)
498            {
499                if (it->getComparable() != "")
500                {
[7401]501                    // 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.
[7233]502                    if (it->hasDisplay())
503                        return (it->getString());
504                    else
505                        return (it->getString() + ' ');
506                }
507            }
508
509            return "";
[7228]510        }
511        else
512        {
[7401]513            // multiple arguments in the list - iterate through it and find the common begin of all arguments
[7228]514            std::string output;
515            for (unsigned int i = 0; true; i++)
516            {
[7401]517                char tempComparable = '\0';
518                char temp = '\0';
[7228]519                for (ArgumentCompletionList::const_iterator it = list.begin(); it != list.end(); ++it)
520                {
521                    const std::string& argumentComparable = it->getComparable();
522                    const std::string& argument = it->getString();
[7233]523
[7401]524                    // ignore empty arguments
[7233]525                    if (argumentComparable == "")
526                        continue;
527
[7228]528                    if (argument.size() > i)
529                    {
[7401]530                        if (tempComparable == '\0')
[7228]531                        {
[7401]532                            // the first entry is always taken
[7228]533                            tempComparable = argumentComparable[i];
534                            temp = argument[i];
535                        }
536                        else
537                        {
[7401]538                            // all other entries need comparison to the first entry
[7228]539                            if (tempComparable != argumentComparable[i])
540                                return output;
[7401]541                            else if (temp != argument[i]) // the comparables match, but the normal chars don't - switch to comparable only
[7228]542                                temp = tempComparable;
543                        }
544                    }
545                    else
546                    {
547                        return output;
548                    }
549                }
550                output += temp;
551            }
552            return output;
553        }
554    }
[7235]555
[7401]556    /**
557        @brief Joins the elements of the given list to a string.
558    */
[7235]559    /* static */ std::string CommandEvaluation::dump(const ArgumentCompletionList& list)
560    {
561        std::string output;
562        for (ArgumentCompletionList::const_iterator it = list.begin(); it != list.end(); ++it)
563        {
564            output += it->getDisplay();
565
[7401]566            // add a space character between two elements for all non-empty arguments
[7235]567            if (it->getComparable() != "")
568                output += ' ';
569        }
570        return output;
571    }
572
[7401]573    /**
574        @brief Returns a string that explains the syntax of the given command.
575    */
[7236]576    /* static */ std::string CommandEvaluation::dump(const ConsoleCommand* command)
[7235]577    {
[7401]578        // get the name of the command
[7235]579        std::string output = command->getName();
[7401]580
581        // check if there are parameters
[7235]582        if (command->getExecutor()->getParamCount() > 0)
583            output += ": ";
584
[7401]585        // iterate through the parameters
[7235]586        for (unsigned int i = 0; i < command->getExecutor()->getParamCount(); i++)
587        {
[7401]588            // separate the parameters with a space character
[7235]589            if (i != 0)
590                output += ' ';
591
[7401]592            // print default values in [], others in {} braces
[7235]593            if (command->getExecutor()->defaultValueSet(i))
594                output += '[';
595            else
596                output += '{';
597
[7401]598            // add the type-name of the parameter
[7235]599            output += command->getExecutor()->getTypenameParam(i);
600
[7401]601            // print the default value if available
[7235]602            if (command->getExecutor()->defaultValueSet(i))
[9550]603                output += '=' + command->getExecutor()->getDefaultValue(i).get<std::string>() + ']';
[7235]604            else
605                output += '}';
606        }
607        return output;
608    }
[1505]609}
Note: See TracBrowser for help on using the repository browser.