Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

  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. }
  471.