Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/command/CommandEvaluation.cc @ 9550

Last change on this file since 9550 was 9550, checked in by landauf, 11 years ago

merged testing branch back to trunk. unbelievable it took me 13 months to finish this chore…

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