Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/command/CommandExecutor.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: 12.2 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 CommandExecutor
32*/
33
34#include "CommandExecutor.h"
35
36#include "ConsoleCommand.h"
37#include "TclBind.h"
38#include "Shell.h"
39
40namespace orxonox
41{
42    static const std::string __CC_CommandExecutor_name = "CommandExecutor";
43    static const std::string __CC_autocomplete_name = "autocomplete";
44
45    SetConsoleCommand(__CC_CommandExecutor_name, __CC_autocomplete_name, &CommandExecutor::_autocomplete)
46        .hide()
47        .argumentCompleter(0, autocompletion::groupsandcommands())
48        .argumentCompleter(1, autocompletion::subcommands());
49
50    SetConsoleCommand("unhide", &CommandExecutor::unhide)
51        .argumentCompleter(0, autocompletion::hiddencommand());
52
53    SetConsoleCommand("alias", &CommandExecutor::alias)
54        .argumentCompleter(1, autocompletion::command());
55
56    /**
57        @brief Returns a reference to the only instance of CommandExecutor.
58    */
59    /* static */ CommandExecutor& CommandExecutor::getInstance()
60    {
61        static CommandExecutor instance;
62        return instance;
63    }
64
65    /**
66        @brief Executes a command.
67        @param command A string containing the command
68        @param useTcl If true, the command is passed to tcl (see TclBind)
69        @return Returns the error-code (see @ref CommandExecutorErrorCodes "error codes")
70    */
71    /* static */ int CommandExecutor::execute(const std::string& command, bool useTcl, bool printErrors)
72    {
73        int error;
74        CommandExecutor::queryMT(command, &error, useTcl);
75        if (error && printErrors)
76            orxout(user_error) << "Can't execute \"" << command << "\", " << CommandExecutor::getErrorDescription(error) << ". (execute)" << endl;
77        return error;
78    }
79
80    /**
81        @brief Executes a command and returns its return-value.
82        @param command A string containing the command
83        @param error A pointer to a value (or NULL) where the error-code should be written to (see @ref CommandExecutorErrorCodes "error codes")
84        @param useTcl If true, the command is passed to tcl (see TclBind)
85        @return Returns the return-value of the command (if any - MultiType::Null otherwise)
86    */
87    /* static */ MultiType CommandExecutor::queryMT(const std::string& command, int* error, bool useTcl)
88    {
89        MultiType result;
90        int error_internal;
91
92        if (useTcl)
93        {
94            // pass the command to tcl
95            result = TclBind::eval(command, &error_internal);
96        }
97        else
98        {
99            CommandEvaluation evaluation;
100
101            // try to get the command evaluation from the cache
102            if (!CommandExecutor::getInstance().getCached(command, evaluation))
103            {
104                // it wasn't in the cache - evaluate the command
105                evaluation = CommandExecutor::evaluate(command);
106
107                // evaluate its arguments
108                evaluation.evaluateArguments();
109
110                // write the evaluation to the cache
111                CommandExecutor::getInstance().cache(command, evaluation);
112            }
113
114            // query the command and return its return-value
115            result = evaluation.query(&error_internal);
116        }
117
118        if (error)
119            *error = error_internal;
120        else if (error_internal)
121            orxout(user_error) << "Can't execute \"" << command << "\", " << CommandExecutor::getErrorDescription(error_internal) << ". (query)" << endl;
122
123        return result;
124    }
125
126    /**
127        @brief Executes a command and returns its return-value as string.
128        @param command A string containing the command
129        @param error A pointer to a value (or NULL) where the error-code should be written to (see @ref CommandExecutorErrorCodes "error codes")
130        @param useTcl If true, the command is passed to tcl (see TclBind)
131        @return Returns the return-value of the command converted to a string (or "" if there's no return value)
132    */
133    /* static */ std::string CommandExecutor::query(const std::string& command, int* error, bool useTcl)
134    {
135        return CommandExecutor::queryMT(command, error, useTcl).get<std::string>();
136    }
137
138    /**
139        @brief Evaluates the given command.
140        @param command A string containing the command
141        @return Returns an instance of CommandEvaluation, which contains the evaluated ConsoleCommand, if the command is valid.
142
143        Evaluates the given command string and returns an instance of CommandEvaluation.
144        If the command is valid, this contains the evaluated ConsoleCommand. Otherwise it
145        can still be used to print hints or complete the command.
146
147        @note Tcl commands can not be evaluated. You have to pass them to execute() or to
148        TclBind directly.
149    */
150    /* static */ CommandEvaluation CommandExecutor::evaluate(const std::string& command)
151    {
152        // initialize the command evaluation
153        CommandEvaluation evaluation;
154        evaluation.initialize(command);
155
156        // assign the fallback-command to get hints about the possible commands and groups
157        evaluation.hintCommand_ = ConsoleCommand::getCommand(__CC_CommandExecutor_name, __CC_autocomplete_name);
158
159        // check if there's at least one argument
160        if (evaluation.getNumberOfArguments() >= 1)
161        {
162            // try to get a command from the first token
163            evaluation.execCommand_ = ConsoleCommand::getCommandLC(evaluation.getToken(0));
164            if (evaluation.execCommand_)
165                evaluation.execArgumentsOffset_ = 1;
166            else if (evaluation.getNumberOfArguments() >= 2)
167            {
168                // try to get a command from the first two tokens
169                evaluation.execCommand_ = ConsoleCommand::getCommandLC(evaluation.getToken(0), evaluation.getToken(1));
170                if (evaluation.execCommand_)
171                    evaluation.execArgumentsOffset_ = 2;
172            }
173        }
174
175        // if a valid command was found and the user is already entering arguments, overwrite hintCommand_ with execCommand_
176        if (evaluation.execCommand_ && evaluation.getNumberOfArguments() > evaluation.execArgumentsOffset_)
177        {
178            evaluation.hintCommand_ = evaluation.execCommand_;
179            evaluation.hintArgumentsOffset_ = evaluation.execArgumentsOffset_;
180        }
181
182        return evaluation;
183    }
184
185    /**
186        @brief Returns a description of the error code.
187        @param error The error code
188    */
189    /* static */ std::string CommandExecutor::getErrorDescription(int error)
190    {
191        switch (error)
192        {
193            case CommandExecutor::Inexistent:  return "command doesn't exist";
194            case CommandExecutor::Incomplete:  return "not enough arguments given";
195            case CommandExecutor::Deactivated: return "command is not active";
196            case CommandExecutor::Denied:      return "access denied";
197            case CommandExecutor::Error:       return "an error occurred";
198            default: return "";
199        }
200    }
201
202    /**
203        @brief Gets an evaluated command from the cache.
204        @param command The command that should be looked up in the cache
205        @param evaluation Reference to a CommandEvaluation that will be used to return the cached evaluation.
206        @return Returns true if the command was found in the cache
207    */
208    bool CommandExecutor::getCached(const std::string& command, CommandEvaluation& evaluation)
209    {
210        if (Shell::getCacheSize() == 0)
211            return false;
212
213        // check if the command is in the cache
214        std::map<std::string, CacheEntry>::iterator it = this->cache_.find(command);
215        if (it != this->cache_.end())
216        {
217            // update ordered list of cached commands (move it to the front)
218            this->cachelist_.erase(it->second.iterator_);
219            this->cachelist_.push_front(command);
220            it->second.iterator_ = this->cachelist_.begin();
221
222            // assign the cached evaluation
223            evaluation = it->second.evaluation_;
224            return true;
225        }
226        return false;
227    }
228
229    /**
230        @brief Writes a command evaluation for a given command to the cache.
231    */
232    void CommandExecutor::cache(const std::string& command, const CommandEvaluation& evaluation)
233    {
234        if (Shell::getCacheSize() == 0)
235            return;
236
237        // push command to the front of the ordered list
238        this->cachelist_.push_front(command);
239
240        // create a cache entry and store it in the cache
241        CacheEntry entry;
242        entry.evaluation_ = evaluation;
243        entry.iterator_ = this->cachelist_.begin();
244        this->cache_[command] = entry;
245
246        // remove the last command in the ordered list from the cache if it exceeds the maximum size of the cache
247        if (this->cachelist_.size() > Shell::getCacheSize())
248        {
249            this->cache_.erase(this->cachelist_.back());
250            this->cachelist_.pop_back();
251        }
252    }
253
254    /**
255        @brief This function is used as console command which executes commands that are usually hidden.
256
257        The argument completion function of this console commands is used to find
258        and enter hidden commands and their arguments.
259    */
260    /* static */ MultiType CommandExecutor::unhide(const std::string& command)
261    {
262        return CommandExecutor::queryMT(command);
263    }
264
265    /**
266        @brief This function is used as console command which saves a command and optionally also it's arguments as a new console command with a new name.
267        @param alias The name of the new command alias
268        @param command The existing command and (optionally) its arguments
269    */
270    /* static */ void CommandExecutor::alias(const std::string& alias, const std::string& command)
271    {
272        // first check if the given command is valid, else print an error
273        CommandEvaluation evaluation = CommandExecutor::evaluate(command);
274        if (evaluation.isValid())
275        {
276            // it is valid - copy the executor of this command
277            ExecutorPtr executor = new Executor(*evaluation.getConsoleCommand()->getExecutor().get());
278
279            // evaluate the arguments and if this returns no error, store them as default values
280            if (!evaluation.evaluateArguments())
281            {
282                for (size_t i = 0; i < MAX_FUNCTOR_ARGUMENTS; ++i)
283                    executor->setDefaultValue(i, evaluation.getEvaluatedArgument(i));
284            }
285
286            // split the alias in tokens
287            SubString tokens(alias, " ");
288
289            // check if the alias already exists - print an error and return if it does
290            if ((tokens.size() == 1 && ConsoleCommand::getCommand(tokens[0])) || (tokens.size() == 2 && ConsoleCommand::getCommand(tokens[0], tokens[1])))
291            {
292                orxout(user_error) << "A command with name \"" << alias << "\" already exists." << endl;
293                return;
294            }
295
296            // create a new console command with the given alias as its name
297            if (tokens.size() == 1)
298                createConsoleCommand(tokens[0], executor);
299            else if (tokens.size() == 2)
300                createConsoleCommand(tokens[0], tokens[1], executor);
301            else
302                orxout(user_error) << "\"" << alias << "\" is not a valid alias name (must have one or two words)." << endl;
303        }
304        else
305            orxout(user_error) << "\"" << command << "\" is not a valid command (did you mean \"" << evaluation.getCommandSuggestion() << "\"?)." << endl;
306    }
307}
Note: See TracBrowser for help on using the repository browser.