Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/usability/src/libraries/core/command/CommandEvaluation.cc @ 8270

Last change on this file since 8270 was 7401, checked in by landauf, 14 years ago

merged doc branch back to trunk

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