Subversion Repositories Games.Descent

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
1 pmbaty 1
/*
2
 * This file is part of the DXX-Rebirth project <https://www.dxx-rebirth.com/>.
3
 * It is copyright by its individual contributors, as recorded in the
4
 * project's Git history.  See COPYING.txt at the top level for license
5
 * terms and a link to the Git history.
6
 *
7
 */
8
/*
9
 *
10
 * Command parsing and processing
11
 *
12
 */
13
 
14
#include <cstdarg>
15
#include <map>
16
#include <forward_list>
17
#include <stdio.h>
18
#include <stdlib.h>
19
#include <string.h>
20
#include <ctype.h>
21
 
22
#include "dxxerror.h"
23
#include "u_mem.h"
24
#include "strutil.h"
25
#include "inferno.h"
26
#include "console.h"
27
#include "cvar.h"
28
#include "physfsx.h"
29
 
30
#include "compiler-range_for.h"
31
#include <memory>
32
 
33
namespace {
34
 
35
struct cmd_t
36
{
37
        const char    *name;
38
        cmd_handler_t function;
39
        const char    *help_text;
40
};
41
 
42
/* The list of cmds */
43
static std::map<const char *, std::unique_ptr<cmd_t>> cmd_list;
44
 
45
#define ALIAS_NAME_MAX 32
46
struct cmd_alias_t
47
{
48
        char           name[ALIAS_NAME_MAX];
49
        RAIIdmem<char[]> value;
50
};
51
 
52
}
53
 
54
#define CMD_MAX_ALIASES 1024
55
 
56
/* The list of aliases */
57
static std::map<std::string, std::unique_ptr<cmd_alias_t>> cmd_alias_list;
58
 
59
static cmd_t *cmd_findcommand(const char *cmd_name)
60
{
61
        const auto i = cmd_list.find(cmd_name);
62
        return i == cmd_list.end() ? nullptr : i->second.get();
63
}
64
 
65
 
66
static cmd_alias_t *cmd_findalias(const char *alias_name )
67
{
68
        const auto i = cmd_alias_list.find(alias_name);
69
        return i == cmd_alias_list.end() ? nullptr : i->second.get();
70
}
71
 
72
 
73
/* add a new console command */
74
void cmd_addcommand(const char *cmd_name, cmd_handler_t cmd_func, const char *cmd_help_text)
75
{
76
        const auto i = cmd_list.insert(std::make_pair(cmd_name, std::unique_ptr<cmd_t>{}));
77
        if (!i.second)
78
        {
79
                Int3();
80
                con_printf(CON_NORMAL, "command %s already exists, not adding", cmd_name);
81
                return;
82
        }
83
        auto cmd = (i.first->second = std::make_unique<cmd_t>()).get();
84
        /* create command, insert to hashtable */
85
        cmd->name = cmd_name;
86
        cmd->function = cmd_func;
87
        cmd->help_text = cmd_help_text;
88
 
89
        con_printf(CON_DEBUG, "cmd_addcommand: added %s", cmd->name);
90
}
91
 
92
namespace {
93
 
94
struct cmd_queue_t
95
{
96
        RAIIdmem<char[]> command_line;
97
        explicit cmd_queue_t(char *p) :
98
                command_line(p)
99
        {
100
        }
101
};
102
 
103
}
104
 
105
/* The list of commands to be executed */
106
static std::forward_list<cmd_queue_t> cmd_queue;
107
 
108
/* execute a parsed command */
109
static void cmd_execute(unsigned long argc, const char *const *const argv)
110
{
111
        cmd_t *cmd;
112
        cmd_alias_t *alias;
113
 
114
        if ( (cmd = cmd_findcommand(argv[0])) )
115
        {
116
                con_printf(CON_DEBUG, "cmd_execute: executing %s", argv[0]);
117
                cmd->function(argc, argv);
118
                return;
119
        }
120
 
121
        if ( (alias = cmd_findalias(argv[0])) && alias->value )
122
        {
123
                con_printf(CON_DEBUG, "cmd_execute: pushing alias \"%s\": %s", alias->name, alias->value.get());
124
                cmd_insert(alias->value.get());
125
                return;
126
        }
127
 
128
        /* Otherwise */
129
        if (argc < 31)
130
        {  // set value of cvar
131
                const char *new_argv[32];
132
                int i;
133
 
134
                new_argv[0] = "set";
135
                for (i = 0; i < argc; i++)
136
                        new_argv[i+1] = argv[i];
137
                cvar_cmd_set(argc + 1, new_argv);
138
        }
139
}
140
 
141
 
142
/* Parse an input string */
143
static void cmd_parse(char *input)
144
{
145
        char buffer[CMD_MAX_LENGTH];
146
        char *tokens[CMD_MAX_TOKENS];
147
        int num_tokens;
148
        uint_fast32_t i, l;
149
 
150
        Assert(input != NULL);
151
 
152
        /* Strip leading spaces */
153
        while( isspace(*input) ) { ++input; }
154
        buffer[sizeof(buffer) - 1] = 0;
155
        strncpy(buffer, input, sizeof(buffer) - 1);
156
 
157
        //printf("lead strip \"%s\"\n",buffer);
158
        l = strlen(buffer);
159
        /* If command is empty, give up */
160
        if (l==0) return;
161
 
162
        /* Strip trailing spaces */
163
        for (i=l-1; i>0 && isspace(buffer[i]); i--) ;
164
        buffer[i+1] = 0;
165
        //printf("trail strip \"%s\"\n",buffer);
166
 
167
        /* Split into tokens */
168
        l = strlen(buffer);
169
        num_tokens = 1;
170
 
171
        tokens[0] = buffer;
172
        for (i=1; i<l; i++) {
173
                if (buffer[i] == '"') {
174
                        tokens[num_tokens - 1] = &buffer[++i];
175
                        while (i < l && buffer[i] != '"')
176
                                i++;
177
                        buffer[i] = 0;
178
                        continue;
179
                }
180
                if (isspace(buffer[i]) || buffer[i] == '=') {
181
                        buffer[i] = 0;
182
                        while (isspace(buffer[i+1]) && (i+1 < l)) i++;
183
                        tokens[num_tokens++] = &buffer[i+1];
184
                }
185
        }
186
 
187
        /* Check for matching commands */
188
        cmd_execute(num_tokens, tokens);
189
}
190
 
191
 
192
static int cmd_queue_wait;
193
 
194
int cmd_queue_process(void)
195
{
196
        for (;;)
197
        {
198
                if (cmd_queue_wait)
199
                        break;
200
                auto cmd = cmd_queue.begin();
201
                if (cmd == cmd_queue.end())
202
                        break;
203
                auto command_line = std::move(cmd->command_line);
204
                cmd_queue.pop_front();
205
                con_printf(CON_DEBUG, "cmd_queue_process: processing %s", command_line.get());
206
                cmd_parse(command_line.get());  // Note, this may change the queue
207
        }
208
 
209
        if (cmd_queue_wait > 0) {
210
                cmd_queue_wait--;
211
                con_puts(CON_DEBUG, "cmd_queue_process: waiting");
212
                return 1;
213
        }
214
 
215
        return 0;
216
}
217
 
218
 
219
/* execute until there are no commands left */
220
void cmd_queue_flush(void)
221
{
222
        while (cmd_queue_process()) {
223
        }
224
}
225
 
226
 
227
/* Add some commands to the queue to be executed */
228
void cmd_enqueue(int insert, const char *input)
229
{
230
        std::forward_list<cmd_queue_t> l;
231
        char output[CMD_MAX_LENGTH];
232
        char *optr;
233
        auto before_end = [](std::forward_list<cmd_queue_t> &f) {
234
                for (auto i = f.before_begin();;)
235
                {
236
                        auto j = i;
237
                        if (++ i == f.end())
238
                                return j;
239
                }
240
        };
241
        auto iter = before_end(l);
242
        while (*input) {
243
                optr = output;
244
                int quoted = 0;
245
 
246
                /* Strip leading spaces */
247
                while(isspace(*input) || *input == ';')
248
                        input++;
249
 
250
                /* If command is empty, give up */
251
                if (! *input)
252
                        continue;
253
 
254
                /* Find the end of this line (\n, ;, or nul) */
255
                do {
256
                        if (!*input)
257
                                break;
258
                        if (*input == '"') {
259
                                quoted = 1 - quoted;
260
                                continue;
261
                        } else if ( *input == '\n' || (!quoted && *input == ';') ) {
262
                                input++;
263
                                break;
264
                        }
265
                } while ((*optr++ = *input++));
266
                *optr = 0;
267
 
268
                /* make a new queue item, add it to list */
269
                iter = l.emplace_after(iter, cmd_queue_t{d_strdup(output)});
270
                con_printf(CON_DEBUG, "cmd_enqueue: adding %s", output);
271
        }
272
        auto after = insert
273
                /* add our list to the head of the main list */
274
                ? (con_puts(CON_DEBUG, "cmd_enqueue: added to front of list"), cmd_queue.before_begin())
275
                /* add our list to the tail of the main list */
276
                : (con_puts(CON_DEBUG, "cmd_enqueue: added to back of list"), before_end(cmd_queue))
277
        ;
278
        cmd_queue.splice_after(after, std::move(l));
279
}
280
 
281
void cmd_enqueuef(int insert, const char *fmt, ...)
282
{
283
        va_list arglist;
284
        char buf[CMD_MAX_LENGTH];
285
 
286
        va_start (arglist, fmt);
287
        vsnprintf(buf, sizeof(buf), fmt, arglist);
288
        va_end (arglist);
289
 
290
        cmd_enqueue(insert, buf);
291
}
292
 
293
 
294
/* Attempt to autocomplete an input string */
295
const char *cmd_complete(const char *input)
296
{
297
        uint_fast32_t len = strlen(input);
298
 
299
        if (!len)
300
                return NULL;
301
 
302
        range_for (const auto &i, cmd_list)
303
                if (!d_strnicmp(input, i.first, len))
304
                        return i.first;
305
 
306
        range_for (const auto &i, cmd_alias_list)
307
                if (!d_strnicmp(input, i.first.c_str(), len))
308
                        return i.first.c_str();
309
 
310
        return cvar_complete(input);
311
}
312
 
313
 
314
/* alias */
315
static void cmd_alias(unsigned long argc, const char *const *const argv)
316
{
317
        char buf[CMD_MAX_LENGTH] = "";
318
        if (argc < 2) {
319
                con_puts(CON_NORMAL, "aliases:");
320
                range_for (const auto &i, cmd_alias_list)
321
                        con_printf(CON_NORMAL, "%s: %s", i.first.c_str(), i.second->value.get());
322
                return;
323
        }
324
 
325
        if (argc == 2) {
326
                cmd_alias_t *alias;
327
                if ( (alias = cmd_findalias(argv[1])) && alias->value )
328
                {
329
                        con_printf(CON_NORMAL, "%s: %s", alias->name, alias->value.get());
330
                        return;
331
                }
332
 
333
                con_printf(CON_NORMAL, "alias: %s not found", argv[1]);
334
                return;
335
        }
336
 
337
        for (int i = 2; i < argc; i++) {
338
                if (i > 2)
339
                        strncat(buf, " ", sizeof(buf) - strlen(buf) - 1);
340
                strncat(buf, argv[i], sizeof(buf) - strlen(buf) - 1);
341
        }
342
        const auto i = cmd_alias_list.insert(std::make_pair(argv[1], std::unique_ptr<cmd_alias_t>{}));
343
        auto alias = i.first->second.get();
344
        if (i.second)
345
        {
346
                alias = (i.first->second = std::make_unique<cmd_alias_t>()).get();
347
                alias->name[sizeof(alias->name) - 1] = 0;
348
                strncpy(alias->name, argv[1], sizeof(alias->name) - 1);
349
        }
350
        alias->value.reset(d_strdup(buf));
351
}
352
 
353
 
354
/* unalias */
355
static void cmd_unalias(unsigned long argc, const char *const *const argv)
356
{
357
        if (argc < 2 || argc > 2) {
358
                cmd_insertf("help %s", argv[0]);
359
                return;
360
        }
361
 
362
        const char *alias_name = argv[1];
363
        const auto alias = cmd_alias_list.find(alias_name);
364
        if (alias == cmd_alias_list.end())
365
        {
366
                con_printf(CON_NORMAL, "unalias: %s not found", alias_name);
367
                return;
368
        }
369
        cmd_alias_list.erase(alias);
370
}
371
 
372
/* echo to console */
373
static void cmd_echo(unsigned long argc, const char *const *const argv)
374
{
375
        char buf[CMD_MAX_LENGTH] = "";
376
        int i;
377
 
378
        for (i = 1; i < argc; i++) {
379
                if (i > 1)
380
                        strncat(buf, " ", sizeof(buf) - strlen(buf) - 1);
381
                strncat(buf, argv[i], sizeof(buf) - strlen(buf) - 1);
382
        }
383
        con_puts(CON_NORMAL, buf);
384
}
385
 
386
/* execute script */
387
static void cmd_exec(unsigned long argc, const char *const *const argv)
388
{
389
        PHYSFSX_gets_line_t<CMD_MAX_LENGTH> line;
390
 
391
        if (argc < 2 || argc > 2) {
392
                cmd_insertf("help %s", argv[0]);
393
                return;
394
        }
395
        auto f = PHYSFSX_openReadBuffered(argv[1]);
396
        if (!f) {
397
                con_printf(CON_CRITICAL, "exec: %s not found", argv[1]);
398
                return;
399
        }
400
        std::forward_list<cmd_queue_t> l;
401
        auto i = l.before_begin();
402
        while (PHYSFSX_fgets(line, f)) {
403
                /* make a new queue item, add it to list */
404
                i = l.emplace_after(i, cmd_queue_t{d_strdup(line)});
405
                con_printf(CON_DEBUG, "cmd_exec: adding %s", static_cast<const char *>(line));
406
        }
407
 
408
        /* add our list to the head of the main list */
409
        con_puts(CON_DEBUG, "cmd_exec: added to front of list");
410
        cmd_queue.splice_after(cmd_queue.before_begin(), std::move(l));
411
}
412
 
413
/* get help */
414
static void cmd_help(unsigned long argc, const char *const *const argv)
415
{
416
        cmd_t *cmd;
417
 
418
        if (argc > 2) {
419
                cmd_insertf("help %s", argv[0]);
420
                return;
421
        }
422
 
423
        if (argc < 2) {
424
                con_puts(CON_NORMAL, "Available commands:");
425
                range_for (const auto &i, cmd_list)
426
                        con_printf(CON_NORMAL, "    %s", i.first);
427
 
428
                return;
429
        }
430
 
431
        cmd = cmd_findcommand(argv[1]);
432
 
433
        if (!cmd) {
434
                con_printf(CON_URGENT, "Command %s not found", argv[1]);
435
                return;
436
        }
437
 
438
        if (!cmd->help_text) {
439
                con_printf(CON_NORMAL, "%s: no help found", argv[1]);
440
                return;
441
        }
442
 
443
        con_puts(CON_NORMAL, cmd->help_text);
444
}
445
 
446
/* execute script */
447
static void cmd_wait(unsigned long argc, const char *const *const argv)
448
{
449
        if (argc > 2) {
450
                cmd_insertf("help %s", argv[0]);
451
                return;
452
        }
453
 
454
        if (argc < 2)
455
                cmd_queue_wait = 1;
456
        else
457
                cmd_queue_wait = atoi(argv[1]);
458
}
459
 
460
void cmd_init(void)
461
{
462
        cmd_addcommand("alias",     cmd_alias,      "alias <name> <commands>\n" "    define <name> as an alias for <commands>\n"
463
                                                    "alias <name>\n"            "    show the current definition of <name>\n"
464
                                                    "alias\n"                   "    show all defined aliases");
465
        cmd_addcommand("unalias",   cmd_unalias,    "unalias <name>\n"          "    undefine the alias <name>");
466
        cmd_addcommand("echo",      cmd_echo,       "echo [text]\n"             "    write <text> to the console");
467
        cmd_addcommand("exec",      cmd_exec,       "exec <file>\n"             "    execute <file>");
468
        cmd_addcommand("help",      cmd_help,       "help [command]\n"          "    get help for <command>, or list all commands if not specified.");
469
        cmd_addcommand("wait",      cmd_wait,       "usage: wait [n]\n"         "    stop processing commands, resume in <n> cycles (default 1)");
470
}