Support "commands".
[gdbmicli.git] / main.c
diff --git a/main.c b/main.c
index c43df32..f4be40c 100644 (file)
--- a/main.c
+++ b/main.c
@@ -9,16 +9,21 @@
 #include "ansidecl.h"
 #include "mi_gdb.h"
 
+#define fatal(fmt...) fatal_func (__FILE__, __LINE__, __PRETTY_FUNCTION__, fmt)
 #define gdb_assert assert
+#define gdb_assert_not_reached(msg) fatal ("%s", (msg))
+#define _(x) x
 
 static ATTRIBUTE_NORETURN void
-fatal (const char *fmt, ...)
+fatal_func (const char *file, int line, const char *func, const char *fmt, ...)
 {
   va_list ap;
 
+  fprintf (stderr, "%s:%d:%s:", file, line, func);
   va_start (ap, fmt);
   vfprintf (stderr, fmt, ap);
   va_end (ap);
+  fputc ('\n', stderr);
 raise (SIGABRT);
   exit (EXIT_FAILURE);
 }
@@ -34,6 +39,28 @@ xstrdup (const char *s)
   return retval;
 }
 
+static void *
+xmalloc (size_t size)
+{
+  void *p;
+
+  gdb_assert (size != 0);
+  p = malloc (size);
+  gdb_assert (p != NULL);
+  return p;
+}
+
+static void *
+xrealloc (void *p, size_t size)
+{
+  if (p == NULL)
+    return xmalloc (size);
+  gdb_assert (size != 0);
+  p = realloc (p, size);
+  gdb_assert (p != NULL);
+  return p;
+}
+
 static void
 xfree (void *p)
 {
@@ -61,6 +88,14 @@ console_cb (const char *str, void *data)
   fputs (str, stdout);
 }
 
+static int
+time_out_cb (void *data)
+{
+  fatal ("GDB response has timed out");
+  /* NOTREACHED */
+  return 0;
+}
+
 static ATTRIBUTE_UNUSED void
 to_gdb_cb (const char *str, void *data)
 {
@@ -90,16 +125,11 @@ history_save (int rc, void *arg)
 }
 
 static void
-quit_command (mi_h *h, const char *cmd)
-{
-  exit (EXIT_SUCCESS);
-}
-
-static void
 default_command (mi_h *h, const char *cmd)
 {
   int count = 1;
 
+  gdb_assert (strchr (cmd, '\n') == 0);
   mi_send (h, "-interpreter-exec console \"%s\"\n", cmd);
 
   while (count > 0)
@@ -122,6 +152,7 @@ default_command (mi_h *h, const char *cmd)
            count--;
            break;
          case MI_CL_ERROR:
+           count--;
            gdb_assert (rec->c->type == t_const);
            puts (rec->c->v.cstr);
            break;
@@ -133,16 +164,139 @@ default_command (mi_h *h, const char *cmd)
     }
 }
 
+static void
+quit_command (mi_h *h, const char *cmd)
+{
+  exit (EXIT_SUCCESS);
+}
+
+static char *
+mi_escape (const char *cs)
+{
+  char *d, *retval = xmalloc (strlen (cs) * 2 + 1);
+
+  d = retval;
+  while (*cs)
+    {
+      if (*cs == '"' || *cs == '\\')
+       *d++ = '\\';
+      *d++ = *cs++;
+    }
+  *d = '\0';
+  return retval;
+}
+
+static void
+gdb_done (mi_h *h, const char *command)
+{
+  mi_output *res;
+
+  mi_send (h, "%s\n", command);
+  res = mi_get_response_blk (h);
+  gdb_assert (res != NULL && res->next == NULL && res->tclass == MI_CL_DONE
+             && res->c == NULL);
+  mi_free_output (res);
+}
+
+static void
+commands_command_console_cb (const char *str, void *data)
+{
+  char **strptr = data;
+
+  gdb_assert (*strptr == NULL);
+  *strptr = xstrdup (str);
+}
+
+static void
+commands_command (mi_h *h, const char *cmd_param)
+{
+  char *cmd = xstrdup ("");
+  size_t cmd_len = 0;
+  mi_output *res;
+  char *bpnum = NULL;
+  int nesting;
+
+  if (*cmd_param == '\0')
+    {
+      mi_set_console_cb (h, commands_command_console_cb, &bpnum);
+      gdb_done (h, "output $bpnum");
+      mi_set_console_cb (h, console_cb, NULL);
+      gdb_assert (bpnum != NULL);
+      cmd_param = bpnum;
+    }
+
+  printf (_("Type commands for breakpoint(s) %s, one per line.\n"
+           "End with a line saying just \"end\".\n"), cmd_param);
+
+  nesting = 0;
+  while (nesting >= 0)
+    {
+      char *prompt, *data, *data2, *start, *end;
+      size_t data2_len;
+      int do_nest = 0;
+
+      prompt = xstrprintf ("%*s>", (int) nesting);
+      data = readline (prompt);
+      xfree (prompt);
+      if (data == NULL)
+       data = xstrdup ("end");
+
+      for (start = data; isspace (*start); start++);
+      for (end = data + strlen (data); end > start && isspace (end[-1]); end--);
+      data2 = xmalloc (end - start + 1);
+      memcpy (data2, start, end - start);
+      data2[end - start] = '\0';
+      if (strcmp (data2, "python") == 0)
+       do_nest = 1;
+      if (strcmp (data2, "end") == 0)
+       do_nest = -1;
+      for (end = data2; *end && !isspace (*end); end++);
+      *end = '\0';
+
+      /* Here is a bug in GDB, it does not recognize command shortcuts.  */
+      if (strcmp (data2, "while") == 0 || strcmp (data2, "if") == 0
+         || strcmp (data2, "commands") == 0
+         || strcmp (data2, "while-stepping") == 0
+         || strcmp (data2, "stepping") == 0 || strcmp (data2, "ws") == 0)
+       do_nest = 1;
+      xfree (data2);
+
+      nesting += do_nest;
+      if (nesting < 0)
+       break;
+
+      data2 = mi_escape (data);
+      xfree (data);
+      data2_len = strlen (data2);
+      cmd = xrealloc (cmd, cmd_len + 2 + data2_len + 2);
+      cmd[cmd_len] = ' ';
+      cmd[cmd_len + 1] = '"';
+      memcpy (&cmd[cmd_len + 2], data2, data2_len);
+      cmd[cmd_len + 2 + data2_len] = '"';
+      cmd[cmd_len + 2 + data2_len + 1] = '\0';
+      cmd_len += 2 + data2_len + 1;
+      xfree (data2);
+    }
+
+  mi_send (h, "-break-commands %s%s\n", cmd_param, cmd);
+  xfree (cmd);
+  xfree (bpnum);
+
+  res = mi_get_response_blk (h);
+  gdb_assert (res != NULL && res->next == NULL && res->tclass == MI_CL_DONE
+             && res->c == NULL);
+  mi_free_output (res);
+}
+
 static const struct cmd
 {
   const char *name;
+  int min_shortcut;
   void (*func) (mi_h *h, const char *cmd);
 } cmds[] =
 {
-  { "q", quit_command },
-  { "qu", quit_command },
-  { "qui", quit_command },
-  { "quit", quit_command },
+  { "quit", 1, quit_command },
+  { "commands", 4, commands_command },
 };
 
 static void
@@ -158,28 +312,18 @@ executecommand (mi_h *h, const char *cmd)
   while (isalnum (*cs))
     cs++;
   end = cs;
+  while (isspace (*cs))
+    cs++;
 
   for (cmdp = cmds; cmdp < &cmds[LENGTH (cmds)]; cmdp++)
-    if (strlen (cmdp->name) == end - start
-        && strncmp (cmd, cmdp->name, end - start) == 0)
-      return cmdp->func (h, cmd);
+    if (cmdp->min_shortcut <= end - start && end - start <= strlen (cmdp->name)
+        && strncmp (start, cmdp->name, end - start) == 0)
+      return cmdp->func (h, cs);
 
   return default_command (h, cmd);
 }
 
 static void
-gdb_done (mi_h *h, const char *command)
-{
-  mi_output *res;
-
-  mi_send (h, "%s\n", command);
-  res = mi_get_response_blk (h);
-  gdb_assert (res != NULL && res->next == NULL && res->tclass == MI_CL_DONE
-             && res->c == NULL);
-  mi_free_output (res);
-}
-
-static void
 gdb_set_string (mi_h *h, const char *setting, const char *value)
 {
   char *cmd = xstrprintf ("-gdb-set %s %s", setting, value);
@@ -237,35 +381,132 @@ gdb_show_bool (mi_h *h, const char *setting)
   return retval;
 }
 
+static mi_h *completion_entry_function_h;
+
+static char **completion_entry_function_data;
+static size_t completion_entry_function_data_used;
+static size_t completion_entry_function_data_allocated;
+
+static void
+completion_entry_function_console_cb (const char *str, void *data)
+{
+  int line_start = (intptr_t) data;
+  const char *cs;
+  char *s;
+
+  if (completion_entry_function_data_used
+      == completion_entry_function_data_allocated)
+    {
+      if (completion_entry_function_data_allocated > 0)
+       completion_entry_function_data_allocated *= 2;
+      else
+       completion_entry_function_data_allocated = 0x100;
+      completion_entry_function_data = xrealloc (completion_entry_function_data,
+                                 (sizeof (*completion_entry_function_data)
+                                  * completion_entry_function_data_allocated));
+    }
+
+  cs = strchr (str, '\n');
+  if (cs == NULL || cs[1] != '\0')
+    fatal ("Invalid GDB data: %s", str);
+
+  if (strncmp (rl_line_buffer, str, rl_point) != 0)
+    fatal ("Completion GDB data do not match, have \"%.*s\", got \"%.*s\".",
+          (int) rl_point, rl_line_buffer, (int) (cs - str), str);
+
+  s = xmalloc (cs - str - line_start + 1);
+  memcpy (s, &str[line_start], cs - str - line_start);
+  s[cs - str - line_start] = '\0';
+  completion_entry_function_data[completion_entry_function_data_used++] = s;
+}
+
+static char *
+completion_entry_function (const char *text, int matches)
+{
+  mi_h *h = completion_entry_function_h;
+
+  gdb_assert (matches >= 0);
+  if (matches == 0)
+    {
+      mi_output *res;
+      int line_start;
+
+      while (completion_entry_function_data_used)
+       xfree (completion_entry_function_data
+              [--completion_entry_function_data_used]);
+      xfree (completion_entry_function_data);
+      completion_entry_function_data = NULL;
+      completion_entry_function_data_used = 0;
+      completion_entry_function_data_allocated = 0;
+
+      gdb_assert (rl_point >= 0);
+      gdb_assert (strlen (rl_line_buffer) >= rl_point);
+      gdb_assert (strlen (text) <= rl_point);
+      line_start = rl_point - strlen (text);
+      gdb_assert (strncmp (text, &rl_line_buffer[line_start],
+                          strlen (text)) == 0);
+      mi_send (h, "-interpreter-exec console \"complete %.*s\"\n",
+              (int) rl_point, rl_line_buffer);
+
+      mi_set_console_cb (h, completion_entry_function_console_cb,
+                        (void *) (intptr_t) line_start);
+      res = mi_get_response_blk (h);
+      gdb_assert (res != NULL && res->next == NULL && res->tclass == MI_CL_DONE
+                 && res->c == NULL);
+      mi_free_output (res);
+      mi_set_console_cb (h, console_cb, NULL);
+    }
+
+  if (matches < completion_entry_function_data_used)
+    return xstrdup (completion_entry_function_data[matches]);
+  else if (matches == completion_entry_function_data_used)
+    return NULL;
+  else
+    gdb_assert_not_reached ("too many matches");
+}
+
+extern char **mi_gdb_argv;
+extern void (*mi_gdb_start_hook) (mi_h *h);
+
+static void
+start_hook (mi_h *h)
+{
+  on_exit (h_disconnect, h);
+  mi_set_console_cb (h, console_cb, NULL);
+  mi_set_time_out_cb (h, time_out_cb, NULL);
+
+//  mi_set_to_gdb_cb (h, to_gdb_cb, NULL);
+//  mi_set_from_gdb_cb (h, from_gdb_cb, NULL);
+}
+
 int
 main (int argc, char **argv)
 {
   mi_h *h;
-  char *cmd;
-  const char *ex[argc];
-  unsigned ex_count = 0, ex_used = 0;
+  mi_output *res;
 
   setbuf (stdout, NULL);
 
-  while (*++argv != NULL)
-    {
-      if (strcmp (*argv, "-ex") == 0 && argv[1] != NULL)
-       ex[ex_count++] = *++argv;
-      else
-       fatal ("Unknown parameter: %s", *argv);
-    }
+  mi_gdb_argv = xmalloc ((argc + 2) * sizeof (*mi_gdb_argv));
+  memcpy (&mi_gdb_argv[2], &argv[1], argc * sizeof (*mi_gdb_argv));
+  mi_gdb_argv[0] = "gdb";
+  mi_gdb_argv[1] = "--interpreter=mi";
 
-  mi_set_workaround (MI_PSYM_SEARCH, 0);
+  mi_gdb_start_hook = start_hook;
 
   h = mi_connect_local ();
   if (h == NULL)
     fatal ("Cannot connect to GDB");
 
-  on_exit (h_disconnect, h);
+  /* First eat the prompt.  Then run empty command so that additional results
+     from -ex or -x during mi_connect_local are flushed.  */
+  res = mi_get_response_blk (h);
+  gdb_assert (res == NULL);
+  default_command (h, "echo");
 
-  mi_set_console_cb (h, console_cb, NULL);
-//  mi_set_to_gdb_cb (h, to_gdb_cb, NULL);
-//  mi_set_from_gdb_cb (h, from_gdb_cb, NULL);
+  completion_entry_function_h = h;
+  rl_completion_entry_function = completion_entry_function;
+  rl_readline_name = "gdb";        
 
   if (gdb_show_bool (h, "history save"))
     {
@@ -281,19 +522,17 @@ main (int argc, char **argv)
 
   for (;;)
     {
-      if (ex_used < ex_count)
-       {
-         cmd = xstrdup (ex[ex_used++]);
-         printf ("(gdb) %s\n", cmd);
-       }
+      char *prompt, *cmd;
+      
+      prompt = gdb_show_string (h, "prompt");
+      cmd = readline (prompt);
+      xfree (prompt);
+
+      if (cmd == NULL)
+       cmd = xstrdup ("quit");
       else
-       {
-         cmd = readline ("(gdb) ");
-         if (cmd == NULL)
-           cmd = xstrdup ("quit");
-         else
-           add_history (cmd);
-       }
+       add_history (cmd);
+
       executecommand (h, cmd);
       xfree (cmd);
     }