Command line input handling TLC

I didn't manage to usefully split this further into smaller
independent pieces, so:

 - Use "struct buffer" more.

 - Split out the responsibility of composing a complete command line
   from multiple input lines split with backslash

    (
    E.g.:

       (gdb) print \
       1 + \
       2
       $1 = 3
       (gdb)
    )

   to a separate function.  Note we don't need the separate
   readline_input_state and more_to_come globals at all.  They were
   just obfuscating the logic.

 - Factor out the tricky mostly duplicated code in
   command_line_handler and command_line_input.

gdb/ChangeLog
2016-03-09  Pedro Alves  <palves@redhat.com>

	* event-top.c (more_to_come): Delete.
	(struct readline_input_state): Delete.
	(readline_input_state): Delete.
	(get_command_line_buffer): New function.
	(command_handler): Update comments.  Don't handle NULL commands
	here.  Do not execute commented lines.
	(command_line_append_input_line): New function.
	(handle_line_of_input): New function, partly based on
	command_line_handler and command_line_input.
	(command_line_handler): Rewrite.
	* event-top.h (command_handler): New declaration.
	(command_loop): Defer command execution to command_handler.
	(command_line_input): Update comments.  Simplify, using struct
	buffer and handle_line_of_input.
	* top.h (struct buffer): New forward declaration.
	(handle_line_of_input): New declaration.
This commit is contained in:
Pedro Alves
2016-03-09 18:25:00 +00:00
parent 2669cade3d
commit b69d38afde
5 changed files with 230 additions and 329 deletions

View File

@ -1,3 +1,22 @@
2016-03-09 Pedro Alves <palves@redhat.com>
* event-top.c (more_to_come): Delete.
(struct readline_input_state): Delete.
(readline_input_state): Delete.
(get_command_line_buffer): New function.
(command_handler): Update comments. Don't handle NULL commands
here. Do not execute commented lines.
(command_line_append_input_line): New function.
(handle_line_of_input): New function, partly based on
command_line_handler and command_line_input.
(command_line_handler): Rewrite.
* event-top.h (command_handler): New declaration.
(command_loop): Defer command execution to command_handler.
(command_line_input): Update comments. Simplify, using struct
buffer and handle_line_of_input.
* top.h (struct buffer): New forward declaration.
(handle_line_of_input): New declaration.
2016-03-09 Pedro Alves <palves@redhat.com> 2016-03-09 Pedro Alves <palves@redhat.com>
* event-top.c (command_line_handler): Use xfree + xstrdup instead * event-top.c (command_line_handler): Use xfree + xstrdup instead

View File

@ -49,7 +49,6 @@
static void rl_callback_read_char_wrapper (gdb_client_data client_data); static void rl_callback_read_char_wrapper (gdb_client_data client_data);
static void command_line_handler (char *rl); static void command_line_handler (char *rl);
static void change_line_handler (void); static void change_line_handler (void);
static void command_handler (char *command);
static char *top_level_prompt (void); static char *top_level_prompt (void);
/* Signal handlers. */ /* Signal handlers. */
@ -140,20 +139,6 @@ static struct async_signal_handler *sigtstp_token;
#endif #endif
static struct async_signal_handler *async_sigterm_token; static struct async_signal_handler *async_sigterm_token;
/* Structure to save a partially entered command. This is used when
the user types '\' at the end of a command line. This is necessary
because each line of input is handled by a different call to
command_line_handler, and normally there is no state retained
between different calls. */
static int more_to_come = 0;
struct readline_input_state
{
char *linebuffer;
char *linebuffer_ptr;
}
readline_input_state;
/* This hook is called by rl_callback_read_char_wrapper after each /* This hook is called by rl_callback_read_char_wrapper after each
character is processed. */ character is processed. */
void (*after_char_processing_hook) (void); void (*after_char_processing_hook) (void);
@ -383,6 +368,24 @@ top_level_prompt (void)
return xstrdup (prompt); return xstrdup (prompt);
} }
/* Get a pointer to the command line buffer. This is used to
construct a whole line of input from partial input. */
static struct buffer *
get_command_line_buffer (void)
{
static struct buffer line_buffer;
static int line_buffer_initialized;
if (!line_buffer_initialized)
{
buffer_init (&line_buffer);
line_buffer_initialized = 1;
}
return &line_buffer;
}
/* When there is an event ready on the stdin file descriptor, instead /* When there is an event ready on the stdin file descriptor, instead
of calling readline directly throught the callback function, or of calling readline directly throught the callback function, or
instead of calling gdb_readline_no_editing_callback, give gdb a instead of calling gdb_readline_no_editing_callback, give gdb a
@ -436,152 +439,122 @@ async_disable_stdin (void)
} }
/* Handles a gdb command. This function is called by /* Handle a gdb command line. This function is called when
command_line_handler, which has processed one or more input lines handle_line_of_input has concatenated one or more input lines into
into COMMAND. */ a whole command. */
/* NOTE: 1999-04-30 This is the asynchronous version of the command_loop
function. The command_loop function will be obsolete when we void
switch to use the event loop at every execution of gdb. */
static void
command_handler (char *command) command_handler (char *command)
{ {
struct cleanup *stat_chain; struct cleanup *stat_chain;
char *c;
clear_quit_flag (); clear_quit_flag ();
if (instream == stdin) if (instream == stdin)
reinitialize_more_filter (); reinitialize_more_filter ();
/* If readline returned a NULL command, it means that the connection
with the terminal is gone. This happens at the end of a
testsuite run, after Expect has hung up but GDB is still alive.
In such a case, we just quit gdb killing the inferior program
too. */
if (command == 0)
{
printf_unfiltered ("quit\n");
execute_command ("quit", stdin == instream);
}
stat_chain = make_command_stats_cleanup (1); stat_chain = make_command_stats_cleanup (1);
/* Do not execute commented lines. */
for (c = command; *c == ' ' || *c == '\t'; c++)
;
if (c[0] != '#')
{
execute_command (command, instream == stdin); execute_command (command, instream == stdin);
/* Do any commands attached to breakpoint we stopped at. */ /* Do any commands attached to breakpoint we stopped at. */
bpstat_do_actions (); bpstat_do_actions ();
}
do_cleanups (stat_chain); do_cleanups (stat_chain);
} }
/* Handle a complete line of input. This is called by the callback /* Append RL, an input line returned by readline or one of its
mechanism within the readline library. Deal with incomplete emulations, to CMD_LINE_BUFFER. Returns the command line if we
commands as well, by saving the partial input in a global have a whole command line ready to be processed by the command
buffer. */ interpreter or NULL if the command line isn't complete yet (input
line ends in a backslash). Takes ownership of RL. */
/* NOTE: 1999-04-30 This is the asynchronous version of the static char *
command_line_input function; command_line_input will become command_line_append_input_line (struct buffer *cmd_line_buffer, char *rl)
obsolete once we use the event loop as the default mechanism in {
GDB. */ char *cmd;
static void size_t len;
command_line_handler (char *rl)
len = strlen (rl);
if (len > 0 && rl[len - 1] == '\\')
{
/* Don't copy the backslash and wait for more. */
buffer_grow (cmd_line_buffer, rl, len - 1);
cmd = NULL;
}
else
{
/* Copy whole line including terminating null, and we're
done. */
buffer_grow (cmd_line_buffer, rl, len + 1);
cmd = cmd_line_buffer->buffer;
}
/* Allocated in readline. */
xfree (rl);
return cmd;
}
/* Handle a line of input coming from readline.
If the read line ends with a continuation character (backslash),
save the partial input in CMD_LINE_BUFFER (except the backslash),
and return NULL. Otherwise, save the partial input and return a
pointer to CMD_LINE_BUFFER's buffer (null terminated), indicating a
whole command line is ready to be executed.
Returns EOF on end of file.
If REPEAT, handle command repetitions:
- If the input command line is NOT empty, the command returned is
copied into the global 'saved_command_line' var so that it can
be repeated later.
- OTOH, if the input command line IS empty, return the previously
saved command instead of the empty input line.
*/
char *
handle_line_of_input (struct buffer *cmd_line_buffer,
char *rl, int repeat, char *annotation_suffix)
{ {
static char *linebuffer = 0;
static unsigned linelength = 0;
char *p;
char *p1; char *p1;
char *nline; char *cmd;
int repeat = (instream == stdin);
if (rl == NULL)
return (char *) EOF;
cmd = command_line_append_input_line (cmd_line_buffer, rl);
if (cmd == NULL)
return NULL;
/* We have a complete command line now. Prepare for the next
command, but leave ownership of memory to the buffer . */
cmd_line_buffer->used_size = 0;
if (annotation_level > 1 && instream == stdin) if (annotation_level > 1 && instream == stdin)
printf_unfiltered (("\n\032\032post-prompt\n"));
if (linebuffer == 0)
{ {
linelength = 80; printf_unfiltered (("\n\032\032post-"));
linebuffer = (char *) xmalloc (linelength); puts_unfiltered (annotation_suffix);
linebuffer[0] = '\0'; printf_unfiltered (("\n"));
} }
p = linebuffer; #define SERVER_COMMAND_PREFIX "server "
if (startswith (cmd, SERVER_COMMAND_PREFIX))
if (more_to_come)
{ {
strcpy (linebuffer, readline_input_state.linebuffer); /* Note that we don't set `saved_command_line'. Between this
p = readline_input_state.linebuffer_ptr; and the check in dont_repeat, this insures that repeating
xfree (readline_input_state.linebuffer); will still do the right thing. */
more_to_come = 0; return cmd + strlen (SERVER_COMMAND_PREFIX);
}
#ifdef STOP_SIGNAL
if (job_control)
signal (STOP_SIGNAL, handle_stop_sig);
#endif
/* Make sure that all output has been output. Some machines may let
you get away with leaving out some of the gdb_flush, but not
all. */
wrap_here ("");
gdb_flush (gdb_stdout);
gdb_flush (gdb_stderr);
if (source_file_name != NULL)
++source_line_number;
/* If we are in this case, then command_handler will call quit
and exit from gdb. */
if (!rl || rl == (char *) EOF)
{
command_handler (0);
return; /* Lint. */
}
if (strlen (rl) + 1 + (p - linebuffer) > linelength)
{
linelength = strlen (rl) + 1 + (p - linebuffer);
nline = (char *) xrealloc (linebuffer, linelength);
p += nline - linebuffer;
linebuffer = nline;
}
p1 = rl;
/* Copy line. Don't copy null at end. (Leaves line alone
if this was just a newline). */
while (*p1)
*p++ = *p1++;
xfree (rl); /* Allocated in readline. */
if (p > linebuffer && *(p - 1) == '\\')
{
*p = '\0';
p--; /* Put on top of '\'. */
readline_input_state.linebuffer = xstrdup (linebuffer);
readline_input_state.linebuffer_ptr = p;
/* We will not invoke a execute_command if there is more
input expected to complete the command. So, we need to
print an empty prompt here. */
more_to_come = 1;
display_gdb_prompt ("");
return;
}
#ifdef STOP_SIGNAL
if (job_control)
signal (STOP_SIGNAL, SIG_DFL);
#endif
#define SERVER_COMMAND_LENGTH 7
server_command =
(p - linebuffer > SERVER_COMMAND_LENGTH)
&& strncmp (linebuffer, "server ", SERVER_COMMAND_LENGTH) == 0;
if (server_command)
{
/* Note that we don't set `line'. Between this and the check in
dont_repeat, this insures that repeating will still do the
right thing. */
*p = '\0';
command_handler (linebuffer + SERVER_COMMAND_LENGTH);
display_gdb_prompt (0);
return;
} }
/* Do history expansion if that is wished. */ /* Do history expansion if that is wished. */
@ -591,10 +564,11 @@ command_line_handler (char *rl)
char *history_value; char *history_value;
int expanded; int expanded;
*p = '\0'; /* Insert null now. */ expanded = history_expand (cmd, &history_value);
expanded = history_expand (linebuffer, &history_value);
if (expanded) if (expanded)
{ {
size_t len;
/* Print the changes. */ /* Print the changes. */
printf_unfiltered ("%s\n", history_value); printf_unfiltered ("%s\n", history_value);
@ -602,67 +576,81 @@ command_line_handler (char *rl)
if (expanded < 0) if (expanded < 0)
{ {
xfree (history_value); xfree (history_value);
return; return cmd;
} }
if (strlen (history_value) > linelength)
{ /* history_expand returns an allocated string. Just replace
linelength = strlen (history_value) + 1; our buffer with it. */
linebuffer = (char *) xrealloc (linebuffer, linelength); len = strlen (history_value);
xfree (buffer_finish (cmd_line_buffer));
cmd_line_buffer->buffer = history_value;
cmd_line_buffer->buffer_size = len + 1;
cmd = history_value;
} }
strcpy (linebuffer, history_value);
p = linebuffer + strlen (linebuffer);
}
xfree (history_value);
} }
/* If we just got an empty line, and that is supposed to repeat the /* If we just got an empty line, and that is supposed to repeat the
previous command, return the value in the global buffer. */ previous command, return the previously saved command. */
if (repeat && p == linebuffer && *p != '\\') for (p1 = cmd; *p1 == ' ' || *p1 == '\t'; p1++)
{ ;
command_handler (saved_command_line); if (repeat && *p1 == '\0')
display_gdb_prompt (0); return saved_command_line;
return;
}
for (p1 = linebuffer; *p1 == ' ' || *p1 == '\t'; p1++); /* Add command to history if appropriate. Note: lines consisting
if (repeat && !*p1) solely of comments are also added to the command history. This
{ is useful when you type a command, and then realize you don't
command_handler (saved_command_line); want to execute it quite yet. You can comment out the command
display_gdb_prompt (0); and then later fetch it from the value history and remove the
return; '#'. The kill ring is probably better, but some people are in
} the habit of commenting things out. */
if (*cmd != '\0' && input_from_terminal_p ())
*p = 0; gdb_add_history (cmd);
/* Add line to history if appropriate. */
if (*linebuffer && input_from_terminal_p ())
gdb_add_history (linebuffer);
/* Note: lines consisting solely of comments are added to the command
history. This is useful when you type a command, and then
realize you don't want to execute it quite yet. You can comment
out the command and then later fetch it from the value history
and remove the '#'. The kill ring is probably better, but some
people are in the habit of commenting things out. */
if (*p1 == '#')
*p1 = '\0'; /* Found a comment. */
/* Save into global buffer if appropriate. */ /* Save into global buffer if appropriate. */
if (repeat) if (repeat)
{ {
xfree (saved_command_line); xfree (saved_command_line);
saved_command_line = xstrdup (linebuffer); saved_command_line = xstrdup (cmd);
if (!more_to_come) return saved_command_line;
{
command_handler (saved_command_line);
display_gdb_prompt (0);
} }
return; else
return cmd;
} }
command_handler (linebuffer); /* Handle a complete line of input. This is called by the callback
mechanism within the readline library. Deal with incomplete
commands as well, by saving the partial input in a global
buffer.
NOTE: This is the asynchronous version of the command_line_input
function. */
void
command_line_handler (char *rl)
{
struct buffer *line_buffer = get_command_line_buffer ();
char *cmd;
cmd = handle_line_of_input (line_buffer, rl, instream == stdin, "prompt");
if (cmd == (char *) EOF)
{
/* stdin closed. The connection with the terminal is gone.
This happens at the end of a testsuite run, after Expect has
hung up but GDB is still alive. In such a case, we just quit
gdb killing the inferior program too. */
printf_unfiltered ("quit\n");
execute_command ("quit", stdin == instream);
}
else if (cmd == NULL)
{
/* We don't have a full line yet. Print an empty prompt. */
display_gdb_prompt ("");
}
else
{
command_handler (cmd);
display_gdb_prompt (0); display_gdb_prompt (0);
return; }
} }
/* Does reading of input from terminal w/o the editing features /* Does reading of input from terminal w/o the editing features

View File

@ -34,6 +34,8 @@ extern void async_init_signals (void);
extern void set_async_editing_command (char *args, int from_tty, extern void set_async_editing_command (char *args, int from_tty,
struct cmd_list_element *c); struct cmd_list_element *c);
extern void command_handler (char *command);
/* Signal to catch ^Z typed while reading a command: SIGTSTP or SIGCONT. */ /* Signal to catch ^Z typed while reading a command: SIGTSTP or SIGCONT. */
#ifndef STOP_SIGNAL #ifndef STOP_SIGNAL
#include <signal.h> #include <signal.h>

168
gdb/top.c
View File

@ -533,37 +533,17 @@ execute_command_to_string (char *p, int from_tty)
void void
command_loop (void) command_loop (void)
{ {
struct cleanup *old_chain;
char *command;
while (instream && !feof (instream)) while (instream && !feof (instream))
{ {
clear_quit_flag (); char *command;
if (instream == stdin)
reinitialize_more_filter ();
old_chain = make_cleanup (null_cleanup, 0);
/* Get a command-line. This calls the readline package. */ /* Get a command-line. This calls the readline package. */
command = command_line_input (instream == stdin ? command = command_line_input (instream == stdin ?
get_prompt () : (char *) NULL, get_prompt () : (char *) NULL,
instream == stdin, "prompt"); instream == stdin, "prompt");
if (command == 0) if (command == NULL)
{
do_cleanups (old_chain);
return; return;
} command_handler (command);
make_command_stats_cleanup (1);
/* Do not execute commented lines. */
if (command[0] != '#')
{
execute_command (command, instream == stdin);
/* Do any commands attached to breakpoint we are stopped at. */
bpstat_do_actions ();
}
do_cleanups (old_chain);
} }
} }
@ -1016,32 +996,26 @@ gdb_safe_append_history (void)
do_cleanups (old_chain); do_cleanups (old_chain);
} }
/* Read one line from the command input stream `instream' /* Read one line from the command input stream `instream' into a local
into the local static buffer `linebuffer' (whose current length static buffer. The buffer is made bigger as necessary. Returns
is `linelength'). the address of the start of the line.
The buffer is made bigger as necessary.
Returns the address of the start of the line.
NULL is returned for end of file. NULL is returned for end of file.
*If* the instream == stdin & stdin is a terminal, the line read *If* the instream == stdin & stdin is a terminal, the line read is
is copied into the file line saver (global var char *line, copied into the global 'saved_command_line' so that it can be
length linesize) so that it can be duplicated. repeated.
This routine either uses fancy command line editing or This routine either uses fancy command line editing or simple input
simple input as the user has requested. */ as the user has requested. */
char * char *
command_line_input (const char *prompt_arg, int repeat, char *annotation_suffix) command_line_input (const char *prompt_arg, int repeat, char *annotation_suffix)
{ {
static char *linebuffer = 0; static struct buffer cmd_line_buffer;
static unsigned linelength = 0; static int cmd_line_buffer_initialized;
const char *prompt = prompt_arg; const char *prompt = prompt_arg;
char *p; char *cmd;
char *p1;
char *rl;
char *nline;
char got_eof = 0;
/* The annotation suffix must be non-NULL. */ /* The annotation suffix must be non-NULL. */
if (annotation_suffix == NULL) if (annotation_suffix == NULL)
@ -1065,13 +1039,14 @@ command_line_input (const char *prompt_arg, int repeat, char *annotation_suffix)
prompt = local_prompt; prompt = local_prompt;
} }
if (linebuffer == 0) if (!cmd_line_buffer_initialized)
{ {
linelength = 80; buffer_init (&cmd_line_buffer);
linebuffer = (char *) xmalloc (linelength); cmd_line_buffer_initialized = 1;
} }
p = linebuffer; /* Starting a new command line. */
cmd_line_buffer.used_size = 0;
/* Control-C quits instantly if typed while in this loop /* Control-C quits instantly if typed while in this loop
since it should not wait until the user types a newline. */ since it should not wait until the user types a newline. */
@ -1084,6 +1059,8 @@ command_line_input (const char *prompt_arg, int repeat, char *annotation_suffix)
while (1) while (1)
{ {
char *rl;
/* Make sure that all output has been output. Some machines may /* Make sure that all output has been output. Some machines may
let you get away with leaving out some of the gdb_flush, but let you get away with leaving out some of the gdb_flush, but
not all. */ not all. */
@ -1115,37 +1092,16 @@ command_line_input (const char *prompt_arg, int repeat, char *annotation_suffix)
rl = gdb_readline_no_editing (prompt); rl = gdb_readline_no_editing (prompt);
} }
if (annotation_level > 1 && instream == stdin) cmd = handle_line_of_input (&cmd_line_buffer, rl,
repeat, annotation_suffix);
if (cmd == (char *) EOF)
{ {
puts_unfiltered ("\n\032\032post-"); cmd = NULL;
puts_unfiltered (annotation_suffix);
puts_unfiltered ("\n");
}
if (!rl || rl == (char *) EOF)
{
got_eof = 1;
break; break;
} }
if (strlen (rl) + 1 + (p - linebuffer) > linelength) if (cmd != NULL)
{
linelength = strlen (rl) + 1 + (p - linebuffer);
nline = (char *) xrealloc (linebuffer, linelength);
p += nline - linebuffer;
linebuffer = nline;
}
p1 = rl;
/* Copy line. Don't copy null at end. (Leaves line alone
if this was just a newline). */
while (*p1)
*p++ = *p1++;
xfree (rl); /* Allocated in readline. */
if (p == linebuffer || *(p - 1) != '\\')
break; break;
p--; /* Put on top of '\'. */
prompt = NULL; prompt = NULL;
} }
@ -1155,77 +1111,7 @@ command_line_input (const char *prompt_arg, int repeat, char *annotation_suffix)
#endif #endif
immediate_quit--; immediate_quit--;
if (got_eof) return cmd;
return NULL;
#define SERVER_COMMAND_LENGTH 7
server_command =
(p - linebuffer > SERVER_COMMAND_LENGTH)
&& strncmp (linebuffer, "server ", SERVER_COMMAND_LENGTH) == 0;
if (server_command)
{
/* Note that we don't set `line'. Between this and the check in
dont_repeat, this insures that repeating will still do the
right thing. */
*p = '\0';
return linebuffer + SERVER_COMMAND_LENGTH;
}
/* Do history expansion if that is wished. */
if (history_expansion_p && instream == stdin
&& ISATTY (instream))
{
char *history_value;
int expanded;
*p = '\0'; /* Insert null now. */
expanded = history_expand (linebuffer, &history_value);
if (expanded)
{
/* Print the changes. */
printf_unfiltered ("%s\n", history_value);
/* If there was an error, call this function again. */
if (expanded < 0)
{
xfree (history_value);
return command_line_input (prompt, repeat,
annotation_suffix);
}
if (strlen (history_value) > linelength)
{
linelength = strlen (history_value) + 1;
linebuffer = (char *) xrealloc (linebuffer, linelength);
}
strcpy (linebuffer, history_value);
p = linebuffer + strlen (linebuffer);
}
xfree (history_value);
}
/* If we just got an empty line, and that is supposed to repeat the
previous command, return the value in the global buffer. */
if (repeat && p == linebuffer)
return saved_command_line;
for (p1 = linebuffer; *p1 == ' ' || *p1 == '\t'; p1++);
if (repeat && !*p1)
return saved_command_line;
*p = 0;
/* Add line to history if appropriate. */
if (*linebuffer && input_from_terminal_p ())
gdb_add_history (linebuffer);
/* Save into global buffer if appropriate. */
if (repeat)
{
xfree (saved_command_line);
saved_command_line = xstrdup (linebuffer);
return saved_command_line;
}
return linebuffer;
} }
/* Print the GDB banner. */ /* Print the GDB banner. */

View File

@ -20,6 +20,8 @@
#ifndef TOP_H #ifndef TOP_H
#define TOP_H #define TOP_H
struct buffer;
/* From top.c. */ /* From top.c. */
extern char *saved_command_line; extern char *saved_command_line;
extern FILE *instream; extern FILE *instream;
@ -97,4 +99,8 @@ extern void set_verbose (char *, int, struct cmd_list_element *);
extern void do_restore_instream_cleanup (void *stream); extern void do_restore_instream_cleanup (void *stream);
extern char *handle_line_of_input (struct buffer *cmd_line_buffer,
char *rl, int repeat,
char *annotation_suffix);
#endif #endif