|  | /* CLI utilities. | 
|  |  | 
|  | Copyright (C) 2011-2024 Free Software Foundation, Inc. | 
|  |  | 
|  | 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 "cli/cli-utils.h" | 
|  | #include "value.h" | 
|  |  | 
|  | #include <ctype.h> | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | ULONGEST | 
|  | get_ulongest (const char **pp, int trailer) | 
|  | { | 
|  | LONGEST retval = 0;	/* default */ | 
|  | const char *p = *pp; | 
|  |  | 
|  | if (*p == '$') | 
|  | { | 
|  | value *val = value_from_history_ref (p, &p); | 
|  |  | 
|  | if (val != NULL)	/* Value history reference */ | 
|  | { | 
|  | if (val->type ()->code () == TYPE_CODE_INT) | 
|  | retval = value_as_long (val); | 
|  | else | 
|  | error (_("History value must have integer type.")); | 
|  | } | 
|  | else	/* Convenience variable */ | 
|  | { | 
|  | /* Internal variable.  Make a copy of the name, so we can | 
|  | null-terminate it to pass to lookup_internalvar().  */ | 
|  | const char *start = ++p; | 
|  | while (isalnum (*p) || *p == '_') | 
|  | p++; | 
|  | std::string varname (start, p - start); | 
|  | if (!get_internalvar_integer (lookup_internalvar (varname.c_str ()), | 
|  | &retval)) | 
|  | error (_("Convenience variable $%s does not have integer value."), | 
|  | varname.c_str ()); | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | const char *end = p; | 
|  | retval = strtoulst (p, &end, 0); | 
|  | if (p == end) | 
|  | { | 
|  | /* There is no number here.  (e.g. "cond a == b").  */ | 
|  | error (_("Expected integer at: %s"), p); | 
|  | } | 
|  | p = end; | 
|  | } | 
|  |  | 
|  | if (!(isspace (*p) || *p == '\0' || *p == trailer)) | 
|  | error (_("Trailing junk at: %s"), p); | 
|  | p = skip_spaces (p); | 
|  | *pp = p; | 
|  | return retval; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | int | 
|  | get_number_trailer (const char **pp, int trailer) | 
|  | { | 
|  | int retval = 0;	/* default */ | 
|  | const char *p = *pp; | 
|  | bool negative = false; | 
|  |  | 
|  | if (*p == '-') | 
|  | { | 
|  | ++p; | 
|  | negative = true; | 
|  | } | 
|  |  | 
|  | if (*p == '$') | 
|  | { | 
|  | struct value *val = value_from_history_ref (p, &p); | 
|  |  | 
|  | if (val)	/* Value history reference */ | 
|  | { | 
|  | if (val->type ()->code () == TYPE_CODE_INT) | 
|  | retval = value_as_long (val); | 
|  | else | 
|  | { | 
|  | gdb_printf (_("History value must have integer type.\n")); | 
|  | retval = 0; | 
|  | } | 
|  | } | 
|  | else	/* Convenience variable */ | 
|  | { | 
|  | /* Internal variable.  Make a copy of the name, so we can | 
|  | null-terminate it to pass to lookup_internalvar().  */ | 
|  | char *varname; | 
|  | const char *start = ++p; | 
|  | LONGEST longest_val; | 
|  |  | 
|  | while (isalnum (*p) || *p == '_') | 
|  | p++; | 
|  | varname = (char *) alloca (p - start + 1); | 
|  | strncpy (varname, start, p - start); | 
|  | varname[p - start] = '\0'; | 
|  | if (get_internalvar_integer (lookup_internalvar (varname), | 
|  | &longest_val)) | 
|  | retval = (int) longest_val; | 
|  | else | 
|  | { | 
|  | gdb_printf (_("Convenience variable must " | 
|  | "have integer value.\n")); | 
|  | retval = 0; | 
|  | } | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | const char *p1 = p; | 
|  | while (*p >= '0' && *p <= '9') | 
|  | ++p; | 
|  | if (p == p1) | 
|  | /* There is no number here.  (e.g. "cond a == b").  */ | 
|  | { | 
|  | /* Skip non-numeric token.  */ | 
|  | while (*p && !isspace((int) *p)) | 
|  | ++p; | 
|  | /* Return zero, which caller must interpret as error.  */ | 
|  | retval = 0; | 
|  | } | 
|  | else | 
|  | retval = atoi (p1); | 
|  | } | 
|  | if (!(isspace (*p) || *p == '\0' || *p == trailer)) | 
|  | { | 
|  | /* Trailing junk: return 0 and let caller print error msg.  */ | 
|  | while (!(isspace (*p) || *p == '\0' || *p == trailer)) | 
|  | ++p; | 
|  | retval = 0; | 
|  | } | 
|  | p = skip_spaces (p); | 
|  | *pp = p; | 
|  | return negative ? -retval : retval; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | int | 
|  | get_number (const char **pp) | 
|  | { | 
|  | return get_number_trailer (pp, '\0'); | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | int | 
|  | get_number (char **pp) | 
|  | { | 
|  | int result; | 
|  | const char *p = *pp; | 
|  |  | 
|  | result = get_number_trailer (&p, '\0'); | 
|  | *pp = (char *) p; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | void | 
|  | report_unrecognized_option_error (const char *command, const char *args) | 
|  | { | 
|  | std::string option = extract_arg (&args); | 
|  |  | 
|  | error (_("Unrecognized option '%s' to %s command.  " | 
|  | "Try \"help %s\"."), option.c_str (), | 
|  | command, command); | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | const char * | 
|  | info_print_args_help (const char *prefix, | 
|  | const char *entity_kind, | 
|  | bool document_n_flag) | 
|  | { | 
|  | return xstrprintf (_("\ | 
|  | %sIf NAMEREGEXP is provided, only prints the %s whose name\n\ | 
|  | matches NAMEREGEXP.\n\ | 
|  | If -t TYPEREGEXP is provided, only prints the %s whose type\n\ | 
|  | matches TYPEREGEXP.  Note that the matching is done with the type\n\ | 
|  | printed by the 'whatis' command.\n\ | 
|  | By default, the command might produce headers and/or messages indicating\n\ | 
|  | why no %s can be printed.\n\ | 
|  | The flag -q disables the production of these headers and messages.%s"), | 
|  | prefix, entity_kind, entity_kind, entity_kind, | 
|  | (document_n_flag ? _("\n\ | 
|  | By default, the command will include non-debug symbols in the output;\n\ | 
|  | these can be excluded using the -n flag.") : "")).release (); | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | number_or_range_parser::number_or_range_parser (const char *string) | 
|  | { | 
|  | init (string); | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | void | 
|  | number_or_range_parser::init (const char *string) | 
|  | { | 
|  | m_cur_tok = string; | 
|  | m_last_retval = 0; | 
|  | m_end_value = 0; | 
|  | m_end_ptr = NULL; | 
|  | m_in_range = false; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | int | 
|  | number_or_range_parser::get_number () | 
|  | { | 
|  | if (m_in_range) | 
|  | { | 
|  | /* All number-parsing has already been done.  Return the next | 
|  | integer value (one greater than the saved previous value). | 
|  | Do not advance the token pointer until the end of range is | 
|  | reached.  */ | 
|  |  | 
|  | if (++m_last_retval == m_end_value) | 
|  | { | 
|  | /* End of range reached; advance token pointer.  */ | 
|  | m_cur_tok = m_end_ptr; | 
|  | m_in_range = false; | 
|  | } | 
|  | } | 
|  | else if (*m_cur_tok != '-') | 
|  | { | 
|  | /* Default case: state->m_cur_tok is pointing either to a solo | 
|  | number, or to the first number of a range.  */ | 
|  | m_last_retval = get_number_trailer (&m_cur_tok, '-'); | 
|  | /* If get_number_trailer has found a '-' preceded by a space, it | 
|  | might be the start of a command option.  So, do not parse a | 
|  | range if the '-' is followed by an alpha or another '-'.  We | 
|  | might also be completing something like | 
|  | "frame apply level 0 -" and we prefer treating that "-" as an | 
|  | option rather than an incomplete range, so check for end of | 
|  | string as well.  */ | 
|  | if (m_cur_tok[0] == '-' | 
|  | && !(isspace (m_cur_tok[-1]) | 
|  | && (isalpha (m_cur_tok[1]) | 
|  | || m_cur_tok[1] == '-' | 
|  | || m_cur_tok[1] == '\0'))) | 
|  | { | 
|  | const char **temp; | 
|  |  | 
|  | /* This is the start of a range (<number1> - <number2>). | 
|  | Skip the '-', parse and remember the second number, | 
|  | and also remember the end of the final token.  */ | 
|  |  | 
|  | temp = &m_end_ptr; | 
|  | m_end_ptr = skip_spaces (m_cur_tok + 1); | 
|  | m_end_value = ::get_number (temp); | 
|  | if (m_end_value < m_last_retval) | 
|  | { | 
|  | error (_("inverted range")); | 
|  | } | 
|  | else if (m_end_value == m_last_retval) | 
|  | { | 
|  | /* Degenerate range (number1 == number2).  Advance the | 
|  | token pointer so that the range will be treated as a | 
|  | single number.  */ | 
|  | m_cur_tok = m_end_ptr; | 
|  | } | 
|  | else | 
|  | m_in_range = true; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | if (isdigit (*(m_cur_tok + 1))) | 
|  | error (_("negative value")); | 
|  | if (*(m_cur_tok + 1) == '$') | 
|  | { | 
|  | /* Convenience variable.  */ | 
|  | m_last_retval = ::get_number (&m_cur_tok); | 
|  | if (m_last_retval < 0) | 
|  | error (_("negative value")); | 
|  | } | 
|  | } | 
|  | return m_last_retval; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | void | 
|  | number_or_range_parser::setup_range (int start_value, int end_value, | 
|  | const char *end_ptr) | 
|  | { | 
|  | gdb_assert (start_value > 0); | 
|  |  | 
|  | m_in_range = true; | 
|  | m_end_ptr = end_ptr; | 
|  | m_last_retval = start_value - 1; | 
|  | m_end_value = end_value; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | bool | 
|  | number_or_range_parser::finished () const | 
|  | { | 
|  | /* Parsing is finished when at end of string or null string, | 
|  | or we are not in a range and not in front of an integer, negative | 
|  | integer, convenience var or negative convenience var.  */ | 
|  | return (m_cur_tok == NULL || *m_cur_tok == '\0' | 
|  | || (!m_in_range | 
|  | && !(isdigit (*m_cur_tok) || *m_cur_tok == '$') | 
|  | && !(*m_cur_tok == '-' | 
|  | && (isdigit (m_cur_tok[1]) || m_cur_tok[1] == '$')))); | 
|  | } | 
|  |  | 
|  | /* Accept a number and a string-form list of numbers such as is | 
|  | accepted by get_number_or_range.  Return TRUE if the number is | 
|  | in the list. | 
|  |  | 
|  | By definition, an empty list includes all numbers.  This is to | 
|  | be interpreted as typing a command such as "delete break" with | 
|  | no arguments.  */ | 
|  |  | 
|  | int | 
|  | number_is_in_list (const char *list, int number) | 
|  | { | 
|  | if (list == NULL || *list == '\0') | 
|  | return 1; | 
|  |  | 
|  | number_or_range_parser parser (list); | 
|  |  | 
|  | if (parser.finished ()) | 
|  | error (_("Arguments must be numbers or '$' variables.")); | 
|  | while (!parser.finished ()) | 
|  | { | 
|  | int gotnum = parser.get_number (); | 
|  |  | 
|  | if (gotnum == 0) | 
|  | error (_("Arguments must be numbers or '$' variables.")); | 
|  | if (gotnum == number) | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | const char * | 
|  | remove_trailing_whitespace (const char *start, const char *s) | 
|  | { | 
|  | while (s > start && isspace (*(s - 1))) | 
|  | --s; | 
|  |  | 
|  | return s; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | std::string | 
|  | extract_arg (const char **arg) | 
|  | { | 
|  | const char *result; | 
|  |  | 
|  | if (!*arg) | 
|  | return std::string (); | 
|  |  | 
|  | /* Find the start of the argument.  */ | 
|  | *arg = skip_spaces (*arg); | 
|  | if (!**arg) | 
|  | return std::string (); | 
|  | result = *arg; | 
|  |  | 
|  | /* Find the end of the argument.  */ | 
|  | *arg = skip_to_space (*arg + 1); | 
|  |  | 
|  | if (result == *arg) | 
|  | return std::string (); | 
|  |  | 
|  | return std::string (result, *arg - result); | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | std::string | 
|  | extract_arg (char **arg) | 
|  | { | 
|  | const char *arg_const = *arg; | 
|  | std::string result; | 
|  |  | 
|  | result = extract_arg (&arg_const); | 
|  | *arg += arg_const - *arg; | 
|  | return result; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | int | 
|  | check_for_argument (const char **str, const char *arg, int arg_len) | 
|  | { | 
|  | if (strncmp (*str, arg, arg_len) == 0 | 
|  | && ((*str)[arg_len] == '\0' || isspace ((*str)[arg_len]))) | 
|  | { | 
|  | *str += arg_len; | 
|  | *str = skip_spaces (*str); | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* See documentation in cli-utils.h.  */ | 
|  |  | 
|  | void | 
|  | validate_flags_qcs (const char *which_command, qcs_flags *flags) | 
|  | { | 
|  | if (flags->cont && flags->silent) | 
|  | error (_("%s: -c and -s are mutually exclusive"), which_command); | 
|  | } | 
|  |  |