Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

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

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

merged branch cpp11_v3 back to trunk

  • Property svn:eol-style set to native
File size: 24.1 KB
Line 
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
29/**
30    @file
31    @brief Implementation of CommandEvaluation
32*/
33
34#include "CommandEvaluation.h"
35
36#include "util/StringUtils.h"
37#include "CommandExecutor.h"
38#include "ConsoleCommand.h"
39#include "ConsoleCommandManager.h"
40
41namespace orxonox
42{
43    /**
44        @brief Constructor: Initializes the command evaluation with an empty command.
45    */
46    CommandEvaluation::CommandEvaluation()
47    {
48        this->initialize("");
49    }
50
51    /**
52        @brief Initializes all values.
53    */
54    void CommandEvaluation::initialize(const std::string& command)
55    {
56        this->execCommand_ = nullptr;
57        this->hintCommand_ = nullptr;
58        this->string_ = command;
59        this->execArgumentsOffset_ = 0;
60        this->hintArgumentsOffset_ = 0;
61        this->bPossibleArgumentsRetrieved_ = false;
62        this->possibleArguments_.clear();
63        this->bEvaluatedArguments_ = false;
64        this->bTriedToEvaluatedArguments_ = false;
65        this->numberOfEvaluatedArguments_ = 0;
66
67        // split the command into tokens
68        this->tokens_.split(command, " ", SubString::WhiteSpaces, false, '\\', true, '"', true, '{', '}', true, '\0');
69    }
70
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    */
74    unsigned int CommandEvaluation::getNumberOfArguments() const
75    {
76        unsigned int count = this->tokens_.size();
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
82            return count;
83    }
84
85    /**
86        @brief Returns the last argument (which is the one the user currently enters into the shell).
87    */
88    const std::string& CommandEvaluation::getLastArgument() const
89    {
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
94            return this->tokens_.back();
95    }
96
97    /**
98        @brief Returns the token with the given index (or a blank string if it doesn't exist).
99    */
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;
106    }
107
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    */
112    int CommandEvaluation::execute()
113    {
114        int error;
115        this->query(&error);
116        return error;
117    }
118
119    /**
120        @brief Executes the command which was evaluated by this object and returns its return-value.
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")
122        @return Returns the result of the command (or MultiType::Null if there is no return value)
123    */
124    MultiType CommandEvaluation::query(int* error)
125    {
126        // check if an error value was passed by reference
127        if (error)
128        {
129            // Determine the error-code and return if it is not Success
130
131            *error = CommandExecutor::Success;
132
133            if (!this->execCommand_)
134                *error = CommandExecutor::Inexistent;
135            else if (!this->execCommand_->isActive())
136                *error = CommandExecutor::Deactivated;
137            else if (!this->execCommand_->hasAccess())
138                *error = CommandExecutor::Denied;
139
140            if (*error != CommandExecutor::Success)
141                return MultiType::Null;
142        }
143
144        // check if it's possible to execute the command
145        if (this->execCommand_ && this->execCommand_->isActive() && this->execCommand_->hasAccess())
146        {
147            // if the arguments weren't evaluated yet, do it now.
148            if (!this->bTriedToEvaluatedArguments_)
149                this->evaluateArguments(false);
150
151            // check if the argument evaluation succeded
152            if (this->bEvaluatedArguments_)
153            {
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;
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_)
158                {
159                    case 0:  return (*this->execCommand_->getExecutor())();
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]);
164                    case 5:
165                    default: return (*this->execCommand_->getExecutor())(this->arguments_[0], this->arguments_[1], this->arguments_[2], this->arguments_[3], this->arguments_[4]);
166                }
167            }
168            else if (error)
169                *error = CommandExecutor::Incomplete;
170        }
171
172        // return a null value in case of an error
173        return MultiType::Null;
174    }
175
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)
182    {
183        this->bTriedToEvaluatedArguments_ = true;
184
185        // check if there's a command to execute
186        if (!this->execCommand_)
187        {
188            if (bPrintError)
189                orxout(internal_error, context::commands) << "Can't evaluate arguments, no console command assigned." << endl;
190            return CommandExecutor::Inexistent;
191        }
192
193        int error;
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
200        if (!error)
201            this->bEvaluatedArguments_ = true;
202        else if (bPrintError)
203            orxout(internal_error, context::commands) << "Can't evaluate arguments, not enough arguments given." << endl;
204
205        return error;
206    }
207
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)
214    {
215        if (index < MAX_FUNCTOR_ARGUMENTS)
216            this->arguments_[index] = arg;
217    }
218
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
224    {
225        if (index < MAX_FUNCTOR_ARGUMENTS)
226            return this->arguments_[index];
227
228        return MultiType::Null;
229    }
230
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    */
239    std::string CommandEvaluation::complete()
240    {
241        // check if it's possible to complete the command
242        if (!this->hintCommand_ || !this->hintCommand_->isActive())
243            return this->string_;
244
245        // get the list of possible arguments
246        if (!this->bPossibleArgumentsRetrieved_)
247            this->retrievePossibleArguments();
248
249        // if the list is empty, return the current command string
250        if (CommandEvaluation::getSize(this->possibleArguments_) == 0)
251        {
252            return this->string_;
253        }
254        else
255        {
256            // get the first part of the command string from the beginning up to the last space character
257            std::string output = this->string_.substr(0, this->string_.find_last_of(' ') + 1);
258
259            // add the common begin of all possible arguments
260            output += CommandEvaluation::getCommonBegin(this->possibleArguments_);
261
262            // return the resulting string
263            return output;
264        }
265    }
266
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    */
274    std::string CommandEvaluation::hint()
275    {
276        // check if it's possible to get hints for this command
277        if (!this->hintCommand_ || !this->hintCommand_->isActive())
278            return "";
279
280        // get the list of possible arguments
281        if (!this->bPossibleArgumentsRetrieved_)
282            this->retrievePossibleArguments();
283
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
287        if (CommandEvaluation::getSize(this->possibleArguments_) > 0 || (!this->possibleArguments_.empty() && this->isValid()))
288            return CommandEvaluation::dump(this->possibleArguments_);
289
290        // at this point there's no valid argument in the list, so check if the command is actually valid
291        if (this->isValid())
292        {
293            // yes it is - return the syntax of the command
294            return CommandEvaluation::dump(this->hintCommand_);
295        }
296        else
297        {
298            // no the command is not valid
299            if (this->getNumberOfArguments() > 2)
300            {
301                // the user typed 2+ arguments, but they don't name a command - print an error
302                return std::string("Error: There is no command with name \"") + this->getToken(0) + " " + this->getToken(1) + "\".";
303            }
304            else
305            {
306                // the user typed 1-2 arguments, check what he tried to type and print a suitable error
307                std::string groupLC = getLowercase(this->getToken(0));
308                for (const auto& mapEntry : ConsoleCommandManager::getInstance().getCommandsLC())
309                    if (mapEntry.first == groupLC)
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            }
314        }
315    }
316
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    */
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
329        // iterate through all groups and their commands and calculate the distance to the current command. keep the best.
330        for (const auto& mapEntryGroup : ConsoleCommandManager::getInstance().getCommandsLC())
331        {
332            if (mapEntryGroup.first != "")
333            {
334                for (const auto& mapEntryName : mapEntryGroup.second)
335                {
336                    std::string command = mapEntryGroup.first + " " + mapEntryName.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
347        // now also iterate through all shortcuts and keep the best if it's better than the one found above.
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())
350        {
351            for (const auto& mapEntry : it_group->second)
352            {
353                std::string command = mapEntry.first;
354                unsigned int distance = getLevenshteinDistance(command, token0_LC);
355                if (distance < nearestDistance)
356                {
357                    nearestCommand = command;
358                    nearestDistance = distance;
359                }
360            }
361        }
362
363        // return the command that's closest to the current one.
364        return nearestCommand;
365    }
366
367    /**
368        @brief Gets the possible arguments for the command in its current state.
369    */
370    void CommandEvaluation::retrievePossibleArguments()
371    {
372        this->bPossibleArgumentsRetrieved_ = true;
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.
375        unsigned int argumentID = std::min(this->getNumberOfArguments() - this->hintArgumentsOffset_, this->hintCommand_->getExecutor()->getParamCount());
376
377        // get the argument completer for the given argument index
378        ArgumentCompleter* ac = this->hintCommand_->getArgumentCompleter(argumentID - 1);
379
380        // check if an argument completer exists
381        if (ac)
382        {
383            MultiType arg[MAX_FUNCTOR_ARGUMENTS];
384
385            // the index of the last argument in the command string that is supported by this argument completer
386            size_t max = this->hintArgumentsOffset_ + this->hintCommand_->getExecutor()->getParamCount();
387
388            // write the argument strings to the argument array (in reversed order, as required by the argument completion function)
389            for (size_t i = 0; i < argumentID; ++i)
390                arg[i] = this->getToken(std::min(this->getNumberOfArguments(), (unsigned int)max) - i - 1);
391
392            // check if there are more arguments given by the user than supported
393            if (this->getNumberOfArguments() > max)
394            {
395                // yes - now check if multiple words are supported by the argument completer
396                if (ac->useMultipleWords())
397                {
398                    // yes - join the surplus arguments
399                    std::string surplusArguments = this->tokens_.subSet(max - 1).join();
400                    if (this->string_[this->string_.size() - 1] == ' ')
401                        surplusArguments += ' ';
402
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
407                    CommandEvaluation::strip(this->possibleArguments_, this->getToken(this->getNumberOfArguments() - 1));
408                }
409                else
410                {
411                    // no - the user typed more arguments than supported, no action
412                }
413            }
414            else
415            {
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]);
421            }
422        }
423    }
424
425    /**
426        @brief Returns the size of an argument completion list - empty ("") arguments are not counted.
427    */
428    /* static */ size_t CommandEvaluation::getSize(const ArgumentCompletionList& list)
429    {
430        size_t count = 0;
431        for (const ArgumentCompletionListElement& element : list)
432            if (element.getComparable() != "")
433                ++count;
434        return count;
435    }
436
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    */
442    /* static */ void CommandEvaluation::strip(ArgumentCompletionList& list, const std::string& fragment)
443    {
444        std::string fragmentLC = getLowercase(fragment);
445
446        // iterate through the list
447        for (ArgumentCompletionList::iterator it = list.begin(); it != list.end(); )
448        {
449            const std::string& entry = it->getComparable();
450
451            // check if the argument is empty - if yes, keep it always in the list
452            if (entry == "")
453            {
454                ++it;
455                continue;
456            }
457
458            // check the length of the argument - arguments smaller than 'fragment' are always erased
459            if (entry.size() < fragmentLC.size())
460            {
461                list.erase(it++);
462            }
463            else
464            {
465                // compare the argument char by char with 'fragment'
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            }
481        }
482    }
483
484    /**
485        @brief Returns the commond begin of all arguments in the list.
486    */
487    /* static */ std::string CommandEvaluation::getCommonBegin(const ArgumentCompletionList& list)
488    {
489        if (CommandEvaluation::getSize(list) == 0)
490        {
491            // no (non-empty) values in the list, return an empty string
492            return "";
493        }
494        else if (CommandEvaluation::getSize(list) == 1)
495        {
496            // only one (non-empty) value in the list - search it and return it
497            for (const ArgumentCompletionListElement& element : list)
498            {
499                if (element.getComparable() != "")
500                {
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.
502                    if (element.hasDisplay())
503                        return (element.getString());
504                    else
505                        return (element.getString() + ' ');
506                }
507            }
508
509            return "";
510        }
511        else
512        {
513            // multiple arguments in the list - iterate through it and find the common begin of all arguments
514            std::string output;
515            for (unsigned int i = 0; true; i++)
516            {
517                char tempComparable = '\0';
518                char temp = '\0';
519                for (const ArgumentCompletionListElement& element : list)
520                {
521                    const std::string& argumentComparable = element.getComparable();
522                    const std::string& argument = element.getString();
523
524                    // ignore empty arguments
525                    if (argumentComparable == "")
526                        continue;
527
528                    if (argument.size() > i)
529                    {
530                        if (tempComparable == '\0')
531                        {
532                            // the first entry is always taken
533                            tempComparable = argumentComparable[i];
534                            temp = argument[i];
535                        }
536                        else
537                        {
538                            // all other entries need comparison to the first entry
539                            if (tempComparable != argumentComparable[i])
540                                return output;
541                            else if (temp != argument[i]) // the comparables match, but the normal chars don't - switch to comparable only
542                                temp = tempComparable;
543                        }
544                    }
545                    else
546                    {
547                        return output;
548                    }
549                }
550                output += temp;
551            }
552            return output;
553        }
554    }
555
556    /**
557        @brief Joins the elements of the given list to a string.
558    */
559    /* static */ std::string CommandEvaluation::dump(const ArgumentCompletionList& list)
560    {
561        std::string output;
562        for (const ArgumentCompletionListElement& element : list)
563        {
564            output += element.getDisplay();
565
566            // add a space character between two elements for all non-empty arguments
567            if (element.getComparable() != "")
568                output += ' ';
569        }
570        return output;
571    }
572
573    /**
574        @brief Returns a string that explains the syntax of the given command.
575    */
576    /* static */ std::string CommandEvaluation::dump(const ConsoleCommand* command)
577    {
578        // get the name of the command
579        std::string output = command->getName();
580
581        // check if there are parameters
582        if (command->getExecutor()->getParamCount() > 0)
583            output += ": ";
584
585        // iterate through the parameters
586        for (unsigned int i = 0; i < command->getExecutor()->getParamCount(); i++)
587        {
588            // separate the parameters with a space character
589            if (i != 0)
590                output += ' ';
591
592            // print default values in [], others in {} braces
593            if (command->getExecutor()->defaultValueSet(i))
594                output += '[';
595            else
596                output += '{';
597
598            // add the type-name of the parameter
599            output += command->getExecutor()->getTypenameParam(i);
600
601            // print the default value if available
602            if (command->getExecutor()->defaultValueSet(i))
603                output += '=' + command->getExecutor()->getDefaultValue(i).get<std::string>() + ']';
604            else
605                output += '}';
606        }
607        return output;
608    }
609}
Note: See TracBrowser for help on using the repository browser.