blob: 8804c98d7b4b41371496a74c2b53c09328f5c6c0 [file] [log] [blame]
/* MI Command Set - MI parser.
Copyright (C) 2000-2024 Free Software Foundation, Inc.
Contributed by Cygnus Solutions (a Red Hat company).
This file is part of GDB.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "mi-cmds.h"
#include "mi-parse.h"
#include "charset.h"
#include <ctype.h>
#include "cli/cli-utils.h"
#include "language.h"
static const char mi_no_values[] = "--no-values";
static const char mi_simple_values[] = "--simple-values";
static const char mi_all_values[] = "--all-values";
/* Like parse_escape, but leave the results as a host char, not a
target char. */
static int
mi_parse_escape (const char **string_ptr)
{
int c = *(*string_ptr)++;
switch (c)
{
case '\n':
return -2;
case 0:
(*string_ptr)--;
return 0;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
{
int i = fromhex (c);
int count = 0;
while (++count < 3)
{
c = (**string_ptr);
if (isdigit (c) && c != '8' && c != '9')
{
(*string_ptr)++;
i *= 8;
i += fromhex (c);
}
else
{
break;
}
}
return i;
}
case 'a':
c = '\a';
break;
case 'b':
c = '\b';
break;
case 'f':
c = '\f';
break;
case 'n':
c = '\n';
break;
case 'r':
c = '\r';
break;
case 't':
c = '\t';
break;
case 'v':
c = '\v';
break;
default:
break;
}
return c;
}
void
mi_parse::parse_argv ()
{
/* If arguments were already computed (or were supplied at
construction), then there's no need to re-compute them. */
if (argv != nullptr)
return;
const char *chp = m_args.c_str ();
int argc = 0;
char **argv = XNEWVEC (char *, argc + 1);
argv[argc] = NULL;
while (1)
{
char *arg;
/* Skip leading white space. */
chp = skip_spaces (chp);
/* Three possibilities: EOF, quoted string, or other text. */
switch (*chp)
{
case '\0':
this->argv = argv;
this->argc = argc;
return;
case '"':
{
/* A quoted string. */
int len;
const char *start = chp + 1;
/* Determine the buffer size. */
chp = start;
len = 0;
while (*chp != '\0' && *chp != '"')
{
if (*chp == '\\')
{
chp++;
if (mi_parse_escape (&chp) <= 0)
{
/* Do not allow split lines or "\000". */
freeargv (argv);
return;
}
}
else
chp++;
len++;
}
/* Insist on a closing quote. */
if (*chp != '"')
{
freeargv (argv);
return;
}
/* Insist on trailing white space. */
if (chp[1] != '\0' && !isspace (chp[1]))
{
freeargv (argv);
return;
}
/* Create the buffer and copy characters in. */
arg = XNEWVEC (char, len + 1);
chp = start;
len = 0;
while (*chp != '\0' && *chp != '"')
{
if (*chp == '\\')
{
chp++;
arg[len] = mi_parse_escape (&chp);
}
else
arg[len] = *chp++;
len++;
}
arg[len] = '\0';
chp++; /* That closing quote. */
break;
}
default:
{
/* An unquoted string. Accumulate all non-blank
characters into a buffer. */
int len;
const char *start = chp;
while (*chp != '\0' && !isspace (*chp))
{
chp++;
}
len = chp - start;
arg = XNEWVEC (char, len + 1);
strncpy (arg, start, len);
arg[len] = '\0';
break;
}
}
/* Append arg to argv. */
argv = XRESIZEVEC (char *, argv, argc + 2);
argv[argc++] = arg;
argv[argc] = NULL;
}
}
mi_parse::~mi_parse ()
{
freeargv (argv);
}
/* See mi-parse.h. */
const char *
mi_parse::args ()
{
/* If args were already computed, or if there is no pre-computed
argv, just return the args. */
if (!m_args.empty () || argv == nullptr)
return m_args.c_str ();
/* Compute args from argv. */
for (int i = 0; i < argc; ++i)
{
if (!m_args.empty ())
m_args += " ";
m_args += argv[i];
}
return m_args.c_str ();
}
/* See mi-parse.h. */
void
mi_parse::set_thread_group (const char *arg, char **endp)
{
if (thread_group != -1)
error (_("Duplicate '--thread-group' option"));
if (*arg != 'i')
error (_("Invalid thread group id"));
arg += 1;
thread_group = strtol (arg, endp, 10);
}
/* See mi-parse.h. */
void
mi_parse::set_thread (const char *arg, char **endp)
{
if (thread != -1)
error (_("Duplicate '--thread' option"));
thread = strtol (arg, endp, 10);
}
/* See mi-parse.h. */
void
mi_parse::set_frame (const char *arg, char **endp)
{
if (frame != -1)
error (_("Duplicate '--frame' option"));
frame = strtol (arg, endp, 10);
}
/* See mi-parse.h. */
void
mi_parse::set_language (const char *arg, const char **endp)
{
std::string lang_name = extract_arg (&arg);
language = language_enum (lang_name.c_str ());
if (language == language_unknown)
error (_("Invalid --language argument: %s"), lang_name.c_str ());
if (endp != nullptr)
*endp = arg;
}
/* See mi-parse.h. */
mi_parse::mi_parse (const char *cmd, std::string *token)
{
const char *chp;
/* Before starting, skip leading white space. */
cmd = skip_spaces (cmd);
/* Find/skip any token and then extract it. */
for (chp = cmd; *chp >= '0' && *chp <= '9'; chp++)
;
*token = std::string (cmd, chp - cmd);
/* This wasn't a real MI command. Return it as a CLI_COMMAND. */
if (*chp != '-')
{
chp = skip_spaces (chp);
this->command = make_unique_xstrdup (chp);
this->op = CLI_COMMAND;
return;
}
/* Extract the command. */
{
const char *tmp = chp + 1; /* discard ``-'' */
for (; *chp && !isspace (*chp); chp++)
;
this->command = make_unique_xstrndup (tmp, chp - tmp);
}
/* Find the command in the MI table. */
this->cmd = mi_cmd_lookup (this->command.get ());
if (this->cmd == NULL)
throw_error (UNDEFINED_COMMAND_ERROR,
_("Undefined MI command: %s"), this->command.get ());
/* Skip white space following the command. */
chp = skip_spaces (chp);
/* Parse the --thread and --frame options, if present. At present,
some important commands, like '-break-*' are implemented by
forwarding to the CLI layer directly. We want to parse --thread
and --frame here, so as not to leave those option in the string
that will be passed to CLI.
Same for the --language option. */
for (;;)
{
const char *option;
size_t as = sizeof ("--all ") - 1;
size_t tgs = sizeof ("--thread-group ") - 1;
size_t ts = sizeof ("--thread ") - 1;
size_t fs = sizeof ("--frame ") - 1;
size_t ls = sizeof ("--language ") - 1;
if (strncmp (chp, "--all ", as) == 0)
{
this->all = 1;
chp += as;
}
/* See if --all is the last token in the input. */
if (strcmp (chp, "--all") == 0)
{
this->all = 1;
chp += strlen (chp);
}
if (strncmp (chp, "--thread-group ", tgs) == 0)
{
char *endp;
option = "--thread-group";
chp += tgs;
this->set_thread_group (chp, &endp);
chp = endp;
}
else if (strncmp (chp, "--thread ", ts) == 0)
{
char *endp;
option = "--thread";
chp += ts;
this->set_thread (chp, &endp);
chp = endp;
}
else if (strncmp (chp, "--frame ", fs) == 0)
{
char *endp;
option = "--frame";
chp += fs;
this->set_frame (chp, &endp);
chp = endp;
}
else if (strncmp (chp, "--language ", ls) == 0)
{
option = "--language";
chp += ls;
this->set_language (chp, &chp);
}
else
break;
if (*chp != '\0' && !isspace (*chp))
error (_("Invalid value for the '%s' option"), option);
chp = skip_spaces (chp);
}
/* Save the rest of the arguments for the command. */
this->m_args = chp;
/* Fully parsed, flag as an MI command. */
this->op = MI_COMMAND;
}
/* See mi-parse.h. */
mi_parse::mi_parse (gdb::unique_xmalloc_ptr<char> command,
std::vector<gdb::unique_xmalloc_ptr<char>> args)
{
this->command = std::move (command);
this->token = "";
if (this->command.get ()[0] != '-')
throw_error (UNDEFINED_COMMAND_ERROR,
_("MI command '%s' does not start with '-'"),
this->command.get ());
/* Find the command in the MI table. */
this->cmd = mi_cmd_lookup (this->command.get () + 1);
if (this->cmd == NULL)
throw_error (UNDEFINED_COMMAND_ERROR,
_("Undefined MI command: %s"), this->command.get ());
/* This over-allocates slightly, but it seems unimportant. */
this->argv = XCNEWVEC (char *, args.size () + 1);
for (size_t i = 0; i < args.size (); ++i)
{
const char *chp = args[i].get ();
/* See if --all is the last token in the input. */
if (strcmp (chp, "--all") == 0)
{
this->all = 1;
}
else if (strcmp (chp, "--thread-group") == 0)
{
++i;
if (i == args.size ())
error ("No argument to '--thread-group'");
this->set_thread_group (args[i].get (), nullptr);
}
else if (strcmp (chp, "--thread") == 0)
{
++i;
if (i == args.size ())
error ("No argument to '--thread'");
this->set_thread (args[i].get (), nullptr);
}
else if (strcmp (chp, "--frame") == 0)
{
++i;
if (i == args.size ())
error ("No argument to '--frame'");
this->set_frame (args[i].get (), nullptr);
}
else if (strcmp (chp, "--language") == 0)
{
++i;
if (i == args.size ())
error ("No argument to '--language'");
this->set_language (args[i].get (), nullptr);
}
else
this->argv[this->argc++] = args[i].release ();
}
/* Fully parsed, flag as an MI command. */
this->op = MI_COMMAND;
}
enum print_values
mi_parse_print_values (const char *name)
{
if (strcmp (name, "0") == 0
|| strcmp (name, mi_no_values) == 0)
return PRINT_NO_VALUES;
else if (strcmp (name, "1") == 0
|| strcmp (name, mi_all_values) == 0)
return PRINT_ALL_VALUES;
else if (strcmp (name, "2") == 0
|| strcmp (name, mi_simple_values) == 0)
return PRINT_SIMPLE_VALUES;
else
error (_("Unknown value for PRINT_VALUES: must be: \
0 or \"%s\", 1 or \"%s\", 2 or \"%s\""),
mi_no_values, mi_all_values, mi_simple_values);
}