// vim: ts=4 sw=4 expandtab ft=c

// Copyright (C) 1998-2012 The University of Melbourne.
// Copyright (C) 2013-2024 The Mercury team.
// This file is distributed under the terms specified in COPYING.LIB.

// This file contains the top level of the code of the internal, in-process
// debugger. The functions implementing the commands themselves are in the
// files mercury_trace_cmd_*.c.
//
// Main author: Zoltan Somogyi.

#ifndef _GNU_SOURCE
   // For the GNU C library we need to define the following in order to make
   // the declarations for the UNIX98 pseudoterminal functions visible.
   // Note that we need to define this *before* stdlib.h is included.
   #define _GNU_SOURCE
#endif

#include "mercury_imp.h"
#include "mercury_layout_util.h"
#include "mercury_array_macros.h"
#include "mercury_getopt.h"
#include "mercury_signal.h"
#include "mercury_builtin_types.h"
#include "mercury_deep_profiling.h"
#include "mercury_runtime_util.h"

#include "mercury_event_spec.h"

#include "mercury_trace.h"
#include "mercury_trace_internal.h"
#include "mercury_trace_cmds.h"
#include "mercury_trace_declarative.h"
#include "mercury_trace_alias.h"
#include "mercury_trace_help.h"
#include "mercury_trace_browse.h"
#include "mercury_trace_spy.h"
#include "mercury_trace_tables.h"
#include "mercury_trace_util.h"
#include "mercury_trace_vars.h"
#include "mercury_trace_hold_vars.h"
#include "mercury_trace_readline.h"
#include "mercury_trace_source.h"
#include "mercury_trace_command_queue.h"

#include "mercury_trace_cmd_forward.h"
#include "mercury_trace_cmd_backward.h"
#include "mercury_trace_cmd_browsing.h"
#include "mercury_trace_cmd_breakpoint.h"
#include "mercury_trace_cmd_queries.h"
#include "mercury_trace_cmd_table_io.h"
#include "mercury_trace_cmd_parameter.h"
#include "mercury_trace_cmd_help.h"
#include "mercury_trace_cmd_dd.h"
#include "mercury_trace_cmd_misc.h"
#include "mercury_trace_cmd_exp.h"
#include "mercury_trace_cmd_developer.h"

#include "mdb.browse.mh"
#include "mdb.listing.mh"
#include "mdb.diff.mh"
#include "mdb.browser_info.mh"
#include "mdb.declarative_execution.mh"
#include "mdbcomp.program_representation.mh"
#include "mdbcomp.slice_and_dice.mh"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <signal.h>

#ifdef MR_HAVE_UNISTD_H
  #include <unistd.h>
#endif

#ifdef MR_HAVE_SYS_TYPES_H
  #include <sys/types.h>
#endif

#ifdef MR_HAVE_SYS_WAIT_H
  #include <sys/wait.h>
#endif

#ifdef MR_HAVE_TERMIOS_H
  #include <termios.h>
#endif

#ifdef MR_HAVE_FCNTL_H
  #include <fcntl.h>
#endif

#ifdef MR_HAVE_SYS_IOCTL_H
  #include <sys/ioctl.h>
#endif

#ifdef MR_HAVE_SYS_STROPTS_H
  #include <sys/stropts.h>
#endif

// Special characters used in mdb commands.
#define DOUBLE_QUOTE_CHAR    '"'
#define SINGLE_QUOTE_CHAR    '\''
#define ESCAPE_CHAR          '\\'

// The initial size of arrays of words.
#define MR_INIT_WORD_COUNT  20

// An upper bound on the maximum number of characters in a number.
// If a number has more than this many chars, the user is in trouble.
#define MR_NUMBER_LEN       80

#define MDBRC_FILENAME          ".mdbrc"
#define DEFAULT_MDBRC_FILENAME  "mdbrc"

// XXX We should consider whether all the static variables in this module
// should be thread local.

// Debugger I/O streams.
// Replacements for stdin/stdout/stderr respectively.
//
// The distinction between MR_mdb_out and MR_mdb_err is analogous to
// the distinction between stdout and stderr: ordinary output, including
// information messages about conditions which are not errors, should
// go to MR_mdb_out, but error messages should go to MR_mdb_err.
//
// Note that MR_mdb_out and MR_mdb_err may both write to the same
// file, so we need to be careful to ensure that buffering does
// not stuff up the interleaving of error messages and ordinary output.
// To ensure this, we do two things:
//
//  - MR_mdb_err is unbuffered
//  - we always fflush(MR_mdb_out) before writing to MR_mdb_err

FILE    *MR_mdb_in;
FILE    *MR_mdb_out;
FILE    *MR_mdb_err;

// MR_have_mdb_window and MR_mdb_window_pid are set by mercury_trace_internal.c ** after the xterm window for mdb has been spawned. The window process is
// killed by MR_trace_internal_kill_mdb_window(), which is called by
// MR_trace_final() through the MR_trace_shutdown() pointer. This indirect call
// is used to avoid references to the non-ISO header file <unistd.h>
// (for pid_t) in the runtime headers.

static  MR_bool     MR_have_mdb_window = MR_FALSE;
static  pid_t       MR_mdb_window_pid = 0;

// The details of the source server, if any.

MR_TraceSourceServer  MR_trace_source_server =
    { NULL, NULL, MR_FALSE };

MR_bool             MR_trace_internal_interacting = MR_FALSE;

MR_bool             MR_trace_echo_queue_commands = MR_FALSE;

static  void        MR_trace_internal_ensure_init(void);
static  MR_bool     MR_trace_internal_create_mdb_window(void);
static  void        MR_trace_internal_kill_mdb_window(void);
static  void        MR_trace_internal_init_from_env(void);
static  void        MR_trace_internal_init_from_local(void);
static  void        MR_trace_internal_init_from_home_dir(void);
static  MR_Next     MR_trace_debug_cmd(char *line, MR_TraceCmdInfo *cmd,
                        MR_EventInfo *event_info, MR_Code **jumpaddr);

static  MR_TraceCmdFunc     MR_trace_handle_cmd;

static  void        MR_mdb_print_proc_id_and_nl(void *data,
                        const MR_ProcLayout *entry_layout);
static  int         MR_trace_var_print_list(MR_SpyPrintList print_list);

static  const char  *MR_trace_parse_line(char *line, char ***words,
                        int *word_max, int *word_count);
static  const char  *MR_trace_break_into_words(char *line, char ***words_ptr,
                        int *word_max_ptr, int *word_count_ptr);
static  const char  *MR_trace_break_off_one_word(char *line, int char_pos,
                        int *new_char_pos_ptr);
static  MR_bool     MR_trace_continue_line(char *ptr, MR_bool *single_quoted,
                        MR_bool *double_quoted);
static  MR_Code     *MR_trace_event_internal_report(MR_TraceCmdInfo *cmd,
                        MR_SpyPrintList print_list,
                        MR_EventInfo *event_info);

static  char        *MR_trace_command_completer_next(const char *word,
                        size_t word_len, MR_CompleterData *data);

MR_SavedDebugState  MR_saved_debug_state;

MR_Code *
MR_trace_event_internal(MR_TraceCmdInfo *cmd, MR_bool interactive,
    MR_SpyPrintList print_list, MR_EventInfo *event_info, const char *msg)
{
    MR_Code     *jumpaddr;
    char        *line;
    MR_Next     res;

    if (! interactive) {
        return MR_trace_event_internal_report(cmd, print_list, event_info);
    }

    // We want to make sure that the Mercury code used to implement some
    // of the debugger's commands (a) doesn't generate any trace events,
    // (b) doesn't generate any unwanted debugging output, and (c) doesn't
    // do any I/O tabling.

    MR_turn_off_debug(&MR_saved_debug_state, MR_FALSE);

    MR_trace_internal_ensure_init();
    MR_trace_browse_ensure_init();
    MR_trace_listing_path_ensure_init();

    if (MR_spy_point_cond_problem != NULL) {
        fprintf(MR_mdb_err, "mdb: couldn't evaluate break point condition\n");
        MR_print_spy_cond(MR_mdb_err, MR_spy_point_cond_bad);
        fprintf(MR_mdb_err, ": %s.\n", MR_spy_point_cond_problem);
        MR_spy_point_cond_bad = NULL;
        MR_spy_point_cond_problem = NULL;
    }

    if (msg != NULL) {
        fprintf(MR_mdb_out, "%s", msg);
    }

    MR_trace_event_print_internal_report(event_info);
    MR_trace_maybe_sync_source_window(event_info, MR_FALSE);

    MR_trace_init_point_vars(event_info->MR_event_sll,
        event_info->MR_saved_regs, event_info->MR_saved_f_regs,
        event_info->MR_trace_port, MR_print_optionals);

    (void) MR_trace_var_print_list(print_list);

    // By default, return where we came from.
    jumpaddr = NULL;

    do {
        line = MR_trace_get_command("mdb> ", MR_mdb_in, MR_mdb_out);
        res = MR_trace_debug_cmd(line, cmd, event_info, &jumpaddr);
        fflush(MR_mdb_err);
    } while (res == KEEP_INTERACTING);

    cmd->MR_trace_must_check = (! cmd->MR_trace_strict) ||
        (cmd->MR_trace_print_level != MR_PRINT_LEVEL_NONE);

#ifdef  MR_TRACE_CHECK_INTEGRITY
    cmd->MR_trace_must_check = cmd->MR_trace_must_check
        || cmd->MR_trace_check_integrity;
#endif

    MR_scroll_next = 0;
    MR_turn_debug_back_on(&MR_saved_debug_state);
    return jumpaddr;
}

static const char MR_trace_banner[] =
"Melbourne Mercury Debugger, mdb version %s.\n\
Copyright 1998-2012 The University of Melbourne.\n\
Copyright 2013-2024 The Mercury team.\n\
mdb is free software; there is absolutely no warranty for mdb.\n";

static FILE *
MR_try_fopen(const char *filename, const char *mode, FILE *default_file)
{
    if (filename == NULL) {
        return default_file;
    } else {
        FILE    *f;
        char    errbuf[MR_STRERROR_BUF_SIZE];

        f = fopen(filename, mode);
        if (f == NULL) {
            fflush(MR_mdb_out);
            fprintf(MR_mdb_err, "mdb: error opening `%s': %s\n",
                filename, MR_strerror(errno, errbuf, sizeof(errbuf)));
            return default_file;
        } else {
            return f;
        }
    }
}

static void
MR_trace_internal_ensure_init(void)
{
    static  MR_bool MR_trace_internal_initialized = MR_FALSE;

    if (! MR_trace_internal_initialized) {
        char        *env;
        MR_Unsigned n;

        if (MR_mdb_benchmark_silent) {
            (void) close(1);
            if (open("/dev/null", O_WRONLY) != 1) {
                fprintf(stderr, "cannot silence stdout");
                exit(1);
            }

            (void) close(2);
            if (open("/dev/null", O_WRONLY) != 2) {
                // There is nowhere to report the error.
                exit(1);
            }
        }

        if (MR_mdb_in_window) {
            // If opening the window fails, fall back on using
            // MR_mdb_*_filename, or stdin, stdout and stderr.

            MR_mdb_in_window = MR_trace_internal_create_mdb_window();
            if (! MR_mdb_in_window) {
                MR_mdb_warning("Try `mdb --program-in-window' instead.\n");
            }
        }

        if (! MR_mdb_in_window) {
            MR_mdb_in = MR_try_fopen(MR_mdb_in_filename, "r", stdin);
            MR_mdb_out = MR_try_fopen(MR_mdb_out_filename, "w", stdout);
            MR_mdb_err = MR_try_fopen(MR_mdb_err_filename, "w", stderr);
        }

        // Ensure that MR_mdb_err is not buffered.
        setvbuf(MR_mdb_err, NULL, _IONBF, 0);

        if (getenv("MERCURY_SUPPRESS_MDB_BANNER") == NULL) {
            fprintf(MR_mdb_out, MR_trace_banner, MR_VERSION);
        }

        if (getenv("MERCURY_DEBUG_ECHO_QUEUE_COMMANDS") != NULL) {
            MR_trace_echo_queue_commands = MR_TRUE;
        }

        env = getenv("LINES");
        if (env != NULL && MR_trace_is_natural_number(env, &n)) {
            MR_scroll_limit = n;
        }

        // We call these functions in this order because we want the .mdbrc
        // file in the current (local) directory to be able to override
        // any actions from the system's standard .mdbrc file, and the
        // .mdbrc file (if any) referred to by the current setting of the
        // MERCURY_DEBUGGER_INIT environment to override both.

        MR_trace_internal_init_from_home_dir();
        MR_trace_internal_init_from_local();
        MR_trace_internal_init_from_env();

        MR_saved_debug_state.MR_sds_io_tabling_enabled = MR_TRUE;
        MR_io_tabling_phase = MR_IO_TABLING_BEFORE;
        MR_io_tabling_start = MR_IO_ACTION_MAX;
        MR_io_tabling_end = MR_IO_ACTION_MAX;

        MR_trace_internal_initialized = MR_TRUE;
    }
}

static volatile sig_atomic_t MR_got_alarm = MR_FALSE;

static void
MR_trace_internal_alarm_handler(void)
{
    MR_got_alarm = MR_TRUE;
}

static MR_bool
MR_trace_internal_create_mdb_window(void)
{
    // XXX The code to find and open a pseudo-terminal is nowhere
    // near as portable as I would like, but given the huge variety
    // of methods for allocating pseudo-terminals it will have to do.
    // Most systems seem to be standardising on this method (from UNIX98).
    // See the xterm or expect source for a more complete version
    // (it's a bit too entwined in the rest of the code to just lift
    // it out and use it here).
    //
    // XXX Add support for MS Windows.

#if defined(MR_HAVE_OPEN) && defined(O_RDWR) && defined(MR_HAVE_FDOPEN) && \
    defined(MR_HAVE_CLOSE) && defined(MR_HAVE_DUP) &&                      \
    defined(MR_HAVE_DUP2) && defined(MR_HAVE_FORK) &&                      \
    defined(MR_HAVE_EXECLP) &&                                             \
    defined(MR_HAVE_GRANTPT) && defined(MR_HAVE_UNLOCKPT) &&               \
    defined(MR_HAVE_PTSNAME) && defined(MR_HAVE_ACCESS) && defined(F_OK)

    int master_fd = -1;
    int slave_fd = -1;
    char *slave_name;
#if defined(MR_HAVE_TERMIOS_H) && defined(MR_HAVE_TCGETATTR) && \
        defined(MR_HAVE_TCSETATTR) && defined(ECHO) && defined(TCSADRAIN)
    struct termios termio;
#endif

    // First check whether /dev/ptmx even exists, so that we can give
    // a slightly better error message if it doesn't.

    if (access("/dev/ptmx", F_OK) != 0) {
        MR_mdb_perror("can't access /dev/ptmx");
        MR_mdb_warning(
            "Sorry, `mdb --window' not supported on this platform.\n");
        return MR_FALSE;
    }

    // OK, /dev/ptmx exists; now go ahead and open it.
    master_fd = open("/dev/ptmx", O_RDWR);
    if (master_fd == -1 || grantpt(master_fd) == -1
        || unlockpt(master_fd) == -1)
    {
        MR_mdb_perror("error opening master pseudo-terminal for mdb window");
        close(master_fd);
        return MR_FALSE;
    }
    if ((slave_name = ptsname(master_fd)) == NULL) {
        MR_mdb_perror("error getting name of pseudo-terminal for mdb window");
        close(master_fd);
        return MR_FALSE;
    }
    slave_fd = open(slave_name, O_RDWR);
    if (slave_fd == -1) {
        close(master_fd);
        MR_mdb_perror("opening slave pseudo-terminal for mdb window failed");
        return MR_FALSE;
    }

#if defined(MR_HAVE_IOCTL) && defined(I_PUSH)
    // Magic STREAMS incantations to make this work on Solaris.
    ioctl(slave_fd, I_PUSH, "ptem");
    ioctl(slave_fd, I_PUSH, "ldterm");
    ioctl(slave_fd, I_PUSH, "ttcompat");
#endif

#if defined(MR_HAVE_TCGETATTR) && defined(MR_HAVE_TCSETATTR) && \
        defined(ECHO) && defined(TCSADRAIN)
    // Turn off echoing before starting the xterm so that the user doesn't see
    // the window ID printed by xterm on startup (this behaviour is not
    // documented in the xterm manual).

    tcgetattr(slave_fd, &termio);
    termio.c_lflag &= ~ECHO;
    tcsetattr(slave_fd, TCSADRAIN, &termio);
#endif

    MR_mdb_window_pid = fork();
    if (MR_mdb_window_pid == -1) {
        MR_mdb_perror("fork() for mdb window failed");
        close(master_fd);
        close(slave_fd);
        return MR_FALSE;
    } else if (MR_mdb_window_pid == 0) {
        // Child - exec() the xterm.

        char xterm_arg[50];

        close(slave_fd);

#if defined(MR_HAVE_SETPGID)
        // Put the xterm in a new process group so it won't be killed
        // by SIGINT signals sent to the program.

        if (setpgid(0, 0) < 0) {
            MR_mdb_perror("setpgid() failed");
            close(master_fd);
            exit(EXIT_FAILURE);
        }
#endif

        // The XX part is required by xterm, but it's not needed for the way
        // we are using xterm (it's meant to be an identifier for the
        // pseudo-terminal). Different versions of xterm use different formats,
        // so it's best to just leave it blank.
        //
        // XXX Some versions of xterm (such as that distributed with
        // XFree86 3.3.6) give a warning about this (but it still works).
        // The latest version distributed with XFree86 4 does not give
        // a warning.

        sprintf(xterm_arg, "-SXX%d", master_fd);

        execlp("xterm", "xterm", "-T", "mdb", xterm_arg, NULL);
        MR_mdb_perror("execution of xterm failed");
        exit(EXIT_FAILURE);
    } else {
        // Parent - set up the mdb I/O streams to point to the pseudo-terminal.

        MR_signal_action    old_alarm_action;
        int                 err_fd = -1;
        int                 out_fd = -1;

        MR_mdb_in = MR_mdb_out = MR_mdb_err = NULL;
        MR_have_mdb_window = MR_TRUE;

        close(master_fd);

        // Read the first line of output -- this is a window ID written by
        // xterm. The alarm() and associated signal handling is to gracefully
        // handle the case where the xterm failed to start, for example
        // because the DISPLAY variable was invalid. We don't want to restart
        // the read() below if it times out.

        MR_get_signal_action(SIGALRM, &old_alarm_action,
            "error retrieving alarm handler");
        MR_setup_signal_no_restart(SIGALRM, MR_trace_internal_alarm_handler,
            MR_FALSE, "error setting up alarm handler");
        MR_got_alarm = MR_FALSE;
        alarm(10);  // 10 second timeout
        while (1) {
            char    c;
            int     status;

            status = read(slave_fd, &c, 1);
            if (status == -1) {
                if (MR_got_alarm) {
                    MR_mdb_warning("timeout starting mdb window");
                    goto parent_error;
                } else if (!MR_is_eintr(errno)) {
                    MR_mdb_perror("error reading from mdb window");
                    goto parent_error;
                }
            } else if (status == 0 || c == '\n') {
                break;
            }
        }

        // Reset the alarm handler.
        alarm(0);
        MR_set_signal_action(SIGALRM, &old_alarm_action,
            "error resetting alarm handler");

#if defined(MR_HAVE_TCGETATTR) && defined(MR_HAVE_TCSETATTR) && \
            defined(ECHO) && defined(TCSADRAIN)
        // Restore echoing.
        termio.c_lflag |= ECHO;
        tcsetattr(slave_fd, TCSADRAIN, &termio);
#endif

        if ((out_fd = dup(slave_fd)) == -1) {
            MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
            goto parent_error;
        }
        if ((err_fd = dup(slave_fd)) == -1) {
            MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
            goto parent_error;
        }

        MR_mdb_in = fdopen(slave_fd, "r");
        if (MR_mdb_in == NULL) {
            MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
            goto parent_error;
        }
        MR_mdb_out = fdopen(out_fd, "w");
        if (MR_mdb_out == NULL) {
            MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
            goto parent_error;
        }
        MR_mdb_err = fdopen(err_fd, "w");
        if (MR_mdb_err == NULL) {
            MR_mdb_perror("opening slave pseudo-terminal for xterm failed");
            goto parent_error;
        }

        MR_have_mdb_window = MR_TRUE;
        MR_trace_shutdown = MR_trace_internal_kill_mdb_window;
        return MR_TRUE;

parent_error:
        MR_trace_internal_kill_mdb_window();
        if (MR_mdb_in) {
            fclose(MR_mdb_in);
        }

        if (MR_mdb_out) {
            fclose(MR_mdb_out);
        }

        if (MR_mdb_err) {
            fclose(MR_mdb_err);
        }

        close(slave_fd);
        close(out_fd);
        close(err_fd);
        return MR_FALSE;

    }

#else   // !MR_HAVE_OPEN, etc.
    MR_mdb_warning("Sorry, `mdb --window' not supported on this platform.\n");
    return MR_FALSE;
#endif // !MR_HAVE_OPEN, etc.
}

static void
MR_trace_internal_kill_mdb_window(void)
{
#if defined(MR_HAVE_KILL) && defined(MR_HAVE_WAIT) && defined(SIGTERM)
    if (MR_have_mdb_window) {
        int status;
        status = kill(MR_mdb_window_pid, SIGTERM);
        if (status != -1) {
            do {
                status = wait(NULL);
                if (status == -1 && !MR_is_eintr(errno)) {
                    break;
                }
            } while (status != MR_mdb_window_pid);
        }
    }
#endif
}

static void
MR_trace_internal_init_from_env(void)
{
    char    *init;

    init = getenv("MERCURY_DEBUGGER_INIT");
    if (init != NULL && strlen(init) > 0) {
        (void) MR_trace_source(init, MR_FALSE, NULL, 0);
        // If the source failed, the error message has been printed.
    }
}

static void
MR_trace_internal_init_from_local(void)
{
    FILE        *fp;
    const char  *init;

    init = MDBRC_FILENAME;
    if ((fp = fopen(init, "r")) != NULL) {
        MR_trace_source_from_open_file(fp, NULL, 0);
        fclose(fp);
    }
}

static void
MR_trace_internal_init_from_home_dir(void)
{
    char    *env;
    char    *buf;
    FILE    *fp;

    // XXX This code is too Unix specific.

    env = getenv("HOME");
    if (env == NULL) {
        return;
    }

    buf = MR_NEW_ARRAY(char, strlen(env) + strlen(MDBRC_FILENAME) + 2);
    (void) strcpy(buf, env);
    (void) strcat(buf, "/");
    (void) strcat(buf, MDBRC_FILENAME);
    if ((fp = fopen(buf, "r")) != NULL) {
        MR_trace_source_from_open_file(fp, NULL, 0);
        fclose(fp);
    }

    MR_free(buf);
}

MR_bool
MR_trace_source(const char *filename, MR_bool ignore_errors,
    char **args, int num_args)
{
    FILE    *fp;
    char    errbuf[MR_STRERROR_BUF_SIZE];

    if ((fp = fopen(filename, "r")) != NULL) {
        MR_trace_source_from_open_file(fp, args, num_args);
        fclose(fp);
        return MR_TRUE;
    }

    if (! ignore_errors) {
        fflush(MR_mdb_out);
        fprintf(MR_mdb_err, "%s: %s.\n",
            filename, MR_strerror(errno, errbuf, sizeof(errbuf)));
    }

    return MR_FALSE;
}

void
MR_trace_source_from_open_file(FILE *fp, char **args, int num_args)
{
    char        *contents;
    MR_CmdLines *line;
    MR_CmdLines *first_line;
    MR_CmdLines *prev_line;

    // Invariant: either both first_line and prev_line are NULL, or neither is.

    first_line = NULL;
    prev_line = NULL;

    // Insert the sourced commands at the front of the command queue,
    // preserving their order in the sourced file.

    while ((contents = MR_trace_readline_from_script(fp, args, num_args))
        != NULL)
    {
        line = MR_NEW(MR_CmdLines);
        line->MR_cmd_line_contents = MR_copy_string(contents);
        line->MR_cmd_line_next = NULL;

        if (first_line == NULL) {
            first_line = line;
        } else {
            MR_assert(prev_line != NULL);
            prev_line->MR_cmd_line_next = line;
        }

        prev_line = line;
    }

    MR_insert_command_lines_at_tail(first_line);

    MR_trace_internal_interacting = MR_FALSE;
}

void
MR_trace_do_noop(void)
{
    fflush(MR_mdb_out);
    fprintf(MR_mdb_err, "This command is a no-op from this port.\n");
}

void
MR_trace_do_noop_tail_rec(void)
{
    fflush(MR_mdb_out);
    fprintf(MR_mdb_err,
        "Due to the reuse of stack frames by tail recursive procedures,\n"
        "this command is a no-op from this port.\n");
}

// This function is just a wrapper for MR_print_proc_id_and_nl,
// with the first argument type being `void *' rather than `FILE *',
// so that this function's address can be passed to
// MR_process_matching_procedures().

MR_Next
MR_trace_cmd_shell(char **words, int word_count, MR_TraceCmdInfo *cmd,
    MR_EventInfo *event_info, MR_Code **jumpaddr)
{
    char*       command_string;
    size_t      command_string_length;
    int         word_num;

    command_string_length = 1;
    for (word_num = 1; word_num < word_count; word_num++) {
        command_string_length += strlen(words[word_num]) + 1;
    }
    command_string = (char*) MR_malloc(sizeof(char) * command_string_length);
    command_string[0] = '\0';
    for (word_num = 1; word_num < word_count; word_num++) {
        strcat(command_string, words[word_num]);
        strcat(command_string, " ");
    }

    MR_trace_call_system_display_error_on_failure(MR_mdb_err, command_string);
    MR_free(command_string);

    return KEEP_INTERACTING;
}

static void
MR_mdb_print_proc_id_and_nl(void *data, const MR_ProcLayout *entry_layout)
{
    FILE    *fp;

    fp = (FILE *) data;
    MR_print_proc_id_and_nl(fp, entry_layout);
}

static int
MR_trace_var_print_list(MR_SpyPrintList print_list)
{
    MR_SpyPrint     node;
    const char      *problem;
    MR_VarSpec      *after_var_spec;
    int             count;

    count = 0;
    for (; print_list != NULL; print_list = print_list->MR_pl_next) {
        count++;
        node = print_list->MR_pl_cur;
        after_var_spec = NULL;

        switch (node->MR_p_what) {
            case MR_SPY_PRINT_ALL:
                problem = MR_trace_browse_all(MR_mdb_out,
                    MR_trace_browse_internal, node->MR_p_format);
                break;

            case MR_SPY_PRINT_GOAL:
                problem = MR_trace_browse_one_goal(MR_mdb_out,
                    MR_trace_browse_goal_internal, MR_BROWSE_CALLER_PRINT,
                    node->MR_p_format);
                break;

            case MR_SPY_PRINT_ONE:
                problem = MR_trace_browse_one_path(MR_mdb_out, MR_TRUE,
                    node->MR_p_var_spec, node->MR_p_path,
                    MR_trace_browse_internal, MR_BROWSE_CALLER_PRINT,
                    node->MR_p_format, MR_FALSE);
                if (problem != NULL &&
                    MR_streq(problem, "there is no such variable"))
                {
                    if (node->MR_p_warn) {
                        problem = "there is no variable named";
                        after_var_spec = &node->MR_p_var_spec;
                    } else {
                        problem = NULL;
                    }
                }

                break;

            default:
                MR_fatal_error("invalid node->MR_p_what");
                break;
        }

        if (problem != NULL) {
            fflush(MR_mdb_out);
            fprintf(MR_mdb_err, "mdb: %s", problem);
            if (after_var_spec != NULL) {
                fprintf(MR_mdb_err, " ");
                MR_print_var_spec(MR_mdb_err, after_var_spec);
            }
            fprintf(MR_mdb_err, ".\n");
        }
    }

    return count;
}

static MR_Next
MR_trace_debug_cmd(char *line, MR_TraceCmdInfo *cmd,
    MR_EventInfo *event_info, MR_Code **jumpaddr)
{
    char        **words;
    char        **orig_words = NULL;
    int         word_max;
    int         word_count;
    const char  *problem;
    MR_Next     next;

    problem = MR_trace_parse_line(line, &words, &word_max, &word_count);
    if (problem != NULL) {
        fflush(MR_mdb_out);
        fprintf(MR_mdb_err, "%s.\n", problem);
        return KEEP_INTERACTING;
    }

    MR_trace_expand_aliases(&words, &word_max, &word_count);

    // At this point, the first word_count members of the words array contain
    // the command. We save the value of words for freeing just before return,
    // since the variable words itself can be overwritten by option processing.

    orig_words = words;

    // Now we check for a special case.

    if (word_count == 0) {
        // Normally EMPTY is aliased to "step", so this won't happen.
        // This can only occur if the user has unaliased EMPTY.
        // In that case, if we get an empty command line, we ignore it.

        next = KEEP_INTERACTING;
    } else {
        // Call the command dispatcher.
        next = MR_trace_handle_cmd(words, word_count, cmd, event_info,
            jumpaddr);
    }

    MR_free(line);
    MR_free(orig_words);

    return next;
}

static  const char  *MR_current_cmd_category;
static  const char  *MR_current_cmd_name;

// IMPORTANT: if you add any new commands, you will need to
//  (a) include them in MR_trace_command_table, defined below.
//  (b) document them in doc/user_guide.texi

static MR_Next
MR_trace_handle_cmd(char **words, int word_count, MR_TraceCmdInfo *cmd,
    MR_EventInfo *event_info, MR_Code **jumpaddr)
{
    const MR_TraceCmdTableEntry *cmd_table_entry;

    // The code for many commands calls getopt, and getopt may print to stderr.
    // We flush MR_mdb_out here to make sure that all normal output so far
    // (including the echoed command, if echoing is turned on) gets output
    // first.

    fflush(MR_mdb_out);

    cmd_table_entry = MR_trace_valid_command(words[0]);
    if (cmd_table_entry != NULL) {
        MR_current_cmd_category = cmd_table_entry->MR_cmd_category;
        MR_current_cmd_name = cmd_table_entry->MR_cmd_name;

        return (*cmd_table_entry->MR_cmd_function)(words, word_count, cmd,
            event_info, jumpaddr);
    } else {
        fflush(MR_mdb_out);
        fprintf(MR_mdb_err, "Unknown command `%s'. "
            "Give the command `help' for help.\n", words[0]);
    }

    return KEEP_INTERACTING;
}

void
MR_trace_usage_cur_cmd(void)
{
    // MR_current_cmd_category is unused now, for but could be used later.
    fflush(MR_mdb_out);
    fprintf(MR_mdb_err,
        "mdb: %s: usage error -- type `help %s' for help.\n",
        MR_current_cmd_name, MR_current_cmd_name);
}

// Given a text line, break it up into words composed of non-space characters
// separated by space characters. Make each word a NULL-terminated string,
// overwriting some spaces in the line array in the process.
//
// If the first word is a number but the second is not, swap the two.
// If the first word has a number prefix, separate it out.
//
// On return *words will point to an array of strings, with space for
// *words_max strings. The number of strings (words) filled in will be
// given by *word_count.
//
// The space for the *words array is allocated with MR_malloc().
// It is the caller's responsibility to free it when appropriate.
// The elements of the *words array point to memory from the line array.
// The lifetime of the elements of the *words array expires when
// the line array is MR_free()'d or further modified or when
// MR_trace_parse_line is called again, whichever comes first.
//
// The return value is NULL if everything went OK, and an error message
// otherwise.

static const char *
MR_trace_parse_line(char *line, char ***words, int *word_max, int *word_count)
{
    char        **raw_words;
    int         raw_word_max;
    int         raw_word_count;
    static char count_buf[MR_NUMBER_LEN + 1];
    char        *s;
    MR_Unsigned i;
    const char  *problem;

    // Handle a possible number prefix on the first word on the line,
    // separating it out into a word on its own.

    problem = MR_trace_break_into_words(line, &raw_words, &raw_word_max,
        &raw_word_count);
    if (problem != NULL) {
        return problem;
    }

    if (raw_word_count > 0 && MR_isdigit(*raw_words[0])) {
        i = 0;
        s = raw_words[0];
        while (MR_isdigit(*s)) {
            if (i >= MR_NUMBER_LEN) {
                return "too large a number";
            }

            count_buf[i] = *s;
            i++;
            s++;
        }

        count_buf[i] = '\0';

        if (*s != '\0') {
            // Only part of the first word constitutes a number.
            // Put it in an extra word at the start.
            MR_ensure_big_enough(raw_word_count, raw_word, char *,
                MR_INIT_WORD_COUNT);

            for (i = raw_word_count; i > 0; i--) {
                raw_words[i] = raw_words[i-1];
            }

            raw_words[0] = count_buf;
            raw_words[1] = s;
            raw_word_count++;
        }
    }

    // If the first word is a number, try to exchange it with the command word,
    // to put the command word first.

    if (raw_word_count > 1 && MR_trace_is_natural_number(raw_words[0], &i)
        && ! MR_trace_is_natural_number(raw_words[1], &i))
    {
        s = raw_words[0];
        raw_words[0] = raw_words[1];
        raw_words[1] = s;
    }

    *words = raw_words;
    *word_max = raw_word_max;
    *word_count = raw_word_count;
    return NULL;
}

// Given a text line, break it up into words. Words are composed of
// non-space characters separated by space characters, except where
// quotes (') or escapes (\) change the treatment of characters. Make
// each word a NULL-terminated string, and remove the quotes and escapes,
// overwriting some parts of the line array in the process.
// XXX The "condition" command would work better if single quotes were
// left in place; that way, users could type "condition X = '+'"
// instead of "condition X = \'+\'".
//
// On return *words will point to an array of strings, with space for
// *words_max strings. The number of strings filled in will be given by
// the return value. The memory for *words is allocated with MR_malloc(),
// and it is the responsibility of the caller to MR_free() it when appropriate.

static const char *
MR_trace_break_into_words(char *line, char ***words_ptr, int *word_max_ptr,
    int *word_count)
{
    int         word_max;
    char        **words;
    int         token_number;
    int         char_pos;
    int         new_char_pos;
    const char  *problem;

    token_number = 0;
    char_pos = 0;

    word_max = 0;
    words = NULL;

    // Each iteration of this loop processes one token, or end of line.
    for (;;) {
        while (line[char_pos] != '\0' && MR_isspace(line[char_pos])) {
            char_pos++;
        }

        if (line[char_pos] == '\0') {
            *words_ptr = words;
            *word_max_ptr = word_max;
            *word_count = token_number;
            return NULL;
        }

        MR_ensure_big_enough(token_number, word, char *, MR_INIT_WORD_COUNT);
        words[token_number] = line + char_pos;
        problem = MR_trace_break_off_one_word(line, char_pos, &new_char_pos);
        if (problem != NULL) {
            return problem;
        }

        char_pos = new_char_pos;
        token_number++;
    }
}

static const char *
MR_trace_break_off_one_word(char *line, int char_pos, int *new_char_pos_ptr)
{
    int         lag = 0;
    MR_bool     single_quoted = MR_FALSE;
    MR_bool     double_quoted = MR_FALSE;
    MR_bool     another = MR_FALSE;

    while (line[char_pos] != '\0') {
        if (! single_quoted && ! double_quoted && MR_isspace(line[char_pos])) {
            another = MR_TRUE;
            break;
        }

        if (! double_quoted && line[char_pos] == SINGLE_QUOTE_CHAR) {
            lag++;
            char_pos++;
            single_quoted = ! single_quoted;
        } else if (! single_quoted && line[char_pos] == DOUBLE_QUOTE_CHAR) {
            if (lag != 0) {
                line[char_pos - lag] = line[char_pos];
            }
            char_pos++;
            double_quoted = ! double_quoted;
        } else {
            if (line[char_pos] == ESCAPE_CHAR) {
                lag++;
                char_pos++;
                if (line[char_pos] == '\0') {
                    return "bad backslash";
                }
            }

            if (lag != 0) {
                line[char_pos - lag] = line[char_pos];
            }
            char_pos++;
        }
    }

    if (single_quoted) {
        return "unmatched single quote";
    }

    if (double_quoted) {
        return "unmatched double quote";
    }

    line[char_pos - lag] = '\0';
    if (another) {
        char_pos++;
    }

    *new_char_pos_ptr = char_pos;
    return NULL;
}

// Call MR_trace_getline to get the next line of input, then do some further
// processing. If the input has reached EOF, return the command "quit".
// If the line contains multiple commands then split it and only return
// the first one. If the newline at the end is either quoted or escaped,
// read another line (using the prompt '>') and append it to the first.
// The command is returned in a MR_malloc'd buffer.

char *
MR_trace_get_command(const char *prompt, FILE *mdb_in, FILE *mdb_out)
{
    char        *line;
    char        *ptr;
    char        *cmd_chars;
    int         cmd_char_max;
    MR_bool     single_quoted;
    MR_bool     double_quoted;
    size_t      len;
    size_t      extra_len;

    line = MR_trace_getline(prompt, mdb_in, mdb_out);

    if (line == NULL) {
        // We got an EOF. We arrange things so we don't have to treat this case
        // specially in the command interpreter.

        line = MR_copy_string("quit");
        return line;
    }

    len = strlen(line);
    ptr = line;
    cmd_chars = line;
    cmd_char_max = len + 1;
    single_quoted = MR_FALSE;
    double_quoted = MR_FALSE;
    while (MR_trace_continue_line(ptr, &single_quoted, &double_quoted)) {
        // We were inside quotes when the end of the line was reached,
        // or the newline was escaped, so input continues on the next line.
        // We append it to the first line, allocating more space if necessary.

        line = MR_trace_getline("> ", mdb_in, mdb_out);
        if (line == NULL) {
            // We got an EOF... we need to stop processing the input,
            // even though it is not syntactically correct, otherwise we might
            // get into an infinite loop if we keep getting EOF.

            break;
        }
        extra_len = strlen(line);
        // cmd_char_max is always > 0
        MR_ensure_big_enough(len + extra_len + 1, cmd_char, char, 0);
        ptr = cmd_chars + len;
        strcpy(ptr, line);
        MR_free(line);
        len = len + extra_len;
    }

    return cmd_chars;
}

// If there any lines waiting in the queue, return the first of these.
// If not, print the prompt to mdb_out, read a line from mdb_in,
// and return it in a MR_malloc'd buffer holding the line (without the final
// newline).
// If EOF occurs on a nonempty line, treat the EOF as a newline; if EOF
// occurs on an empty line, return NULL.
//
// Whether the line is read from the queue or from mdb_in, if this function
// returns a non-NULL value, then the memory for the line returned will have
// been allocated with MR_malloc(), and it is the caller's responsibility
// to MR_free() it when appropriate.

char *
MR_trace_getline(const char *prompt, FILE *mdb_in, FILE *mdb_out)
{
    char    *line;

    line = MR_trace_getline_command_queue();
    if (line != NULL) {
        return line;
    }

    MR_trace_internal_interacting = MR_TRUE;

    line = MR_trace_readline(prompt, mdb_in, mdb_out);

    if (MR_echo_commands && line != NULL) {
        fputs(line, mdb_out);
        putc('\n', mdb_out);
    }

    return line;
}

// This returns MR_TRUE iff the given line continues on to the next line,
// because the newline is in quotes or escaped. The second parameter
// indicates whether we are inside quotes or not, and is updated by
// this function. If an unquoted and unescaped semicolon is encountered,
// the line is split at that point.

static MR_bool
MR_trace_continue_line(char *ptr, MR_bool *single_quoted,
    MR_bool *double_quoted)
{
    MR_bool     escaped = MR_FALSE;

    while (*ptr != '\0') {
        if (escaped) {
            // Do nothing special.
            escaped = MR_FALSE;
        } else if (*ptr == ESCAPE_CHAR) {
            escaped = MR_TRUE;
        } else if (! (*double_quoted) && *ptr == SINGLE_QUOTE_CHAR) {
            *single_quoted = ! (*single_quoted);
        } else if (! (*single_quoted) && *ptr == DOUBLE_QUOTE_CHAR) {
            *double_quoted = ! (*double_quoted);
        } else if (! (*single_quoted) && ! (*double_quoted) && *ptr == ';') {
            // The line contains at least two commands. Return only the first
            // command now; put the others back in the input to be processed
            // later.

            *ptr = '\0';
            MR_insert_command_line_at_head(MR_copy_string(ptr + 1));
            return MR_FALSE;
        }

        ++ptr;
    }

    if (escaped) {
        // Replace the escaped newline with a space.
        *(ptr - 1) = ' ';
    }

    return (*single_quoted || *double_quoted || escaped);
}

static MR_Code *
MR_trace_event_internal_report(MR_TraceCmdInfo *cmd,
    MR_SpyPrintList print_list, MR_EventInfo *event_info)
{
    char            *buf;
    int             i;
    int             len;
    MR_SpyPrintList list;

    list = print_list;
    len = 0;
    for (; list != NULL; list = list->MR_pl_next) {
        len++;
    }

    // We try to leave one line for the prompt itself.
    if (MR_scroll_control && MR_scroll_next + len >= MR_scroll_limit - 1) {
    try_again:
        buf = MR_trace_getline("--more-- ", MR_mdb_in, MR_mdb_out);
        if (buf != NULL) {
            for (i = 0; buf[i] != '\0' && MR_isspace(buf[i]); i++)
                ;

            if (buf[i] != '\0' && !MR_isspace(buf[i])) {
                switch (buf[i]) {
                    case 'a':
                        cmd->MR_trace_print_level_specified = MR_TRUE;
                        cmd->MR_trace_print_level = MR_PRINT_LEVEL_ALL;
                        break;

                    case 'n':
                        cmd->MR_trace_print_level_specified = MR_TRUE;
                        cmd->MR_trace_print_level = MR_PRINT_LEVEL_NONE;
                        break;

                    case 's':
                        cmd->MR_trace_print_level_specified = MR_TRUE;
                        cmd->MR_trace_print_level = MR_PRINT_LEVEL_SOME;
                        break;

                    case 'q':
                        MR_free(buf);
                        return MR_trace_event_internal(cmd, MR_TRUE, NULL,
                            event_info, NULL);

                    default:
                        fflush(MR_mdb_out);
                        fprintf(MR_mdb_err, "unknown command, try again\n");
                        MR_free(buf);
                        goto try_again;
                }
            }

            MR_free(buf);
        }

        MR_scroll_next = 0;
    }

    MR_trace_event_print_internal_report(event_info);
    MR_scroll_next++;

    if (print_list != NULL) {
        MR_trace_init_point_vars(event_info->MR_event_sll,
            event_info->MR_saved_regs, event_info->MR_saved_f_regs,
            event_info->MR_trace_port, MR_print_optionals);
        MR_scroll_next += MR_trace_var_print_list(print_list);
    }

    return NULL;
}

void
MR_trace_event_print_internal_report(MR_EventInfo *event_info)
{
    const MR_LabelLayout    *label_layout;
    const MR_LabelLayout    *parent;
    const char              *filename;
    const char              *parent_filename;
    int                     lineno;
    int                     parent_lineno;
    const char              *problem;                   // Not used.
    MR_Word                 *base_sp;
    MR_Word                 *base_curfr;
    int                     indent;
    const char              *maybe_user_event_name;
    MR_Level                actual_level;

    lineno = 0;
    parent_lineno = 0;
    filename = "";
    parent_filename = "";

    // The code below does has a job that is very similar to the job
    // of the function MR_print_call_trace_info in
    // runtime/mercury_stack_trace.c. Any changes here will probably require
    // similar changes there.

    if (MR_standardize_event_details) {
        char        buf[64];
        MR_Unsigned event_num;
        MR_Unsigned call_num;

        // Do not print the context id. It contains the values of the arguments
        // cast to integers. Since those arguments may originally have been
        // addresses, their values may differ from run to run.

        event_num = MR_standardize_event_num(event_info->MR_event_number);
        call_num = MR_standardize_call_num(event_info->MR_call_seqno);
        MR_snprintf(buf, 64, "E%ld", (long) event_num);
        fprintf(MR_mdb_out, "%8s: ", buf);
        MR_snprintf(buf, 64, "C%ld", (long) call_num);
        fprintf(MR_mdb_out, "%6s ", buf);
        fprintf(MR_mdb_out, "%s",
            MR_simplified_port_names[event_info->MR_trace_port]);
    } else {
#ifdef  MR_USE_MINIMAL_MODEL_OWN_STACKS
        MR_Generator    *generator;
        int             i;

        generator = MR_ENGINE(MR_eng_this_context)->MR_ctxt_owner_generator;
        if (generator != NULL) {
            fprintf(MR_mdb_out, "%s ", MR_gen_subgoal(generator));
        }
#endif

        fprintf(MR_mdb_out, "%8ld: %6ld %2ld %s",
            (long) event_info->MR_event_number,
            (long) event_info->MR_call_seqno,
            (long) event_info->MR_call_depth,
            MR_simplified_port_names[event_info->MR_trace_port]);
    }

    // The printf printed 24 characters.
    indent = 24;

    label_layout = event_info->MR_event_sll;
    (void) MR_find_context(label_layout, &filename, &lineno);
    if (MR_port_is_interface(event_info->MR_trace_port)) {
        base_sp = MR_saved_sp(event_info->MR_saved_regs);
        base_curfr = MR_saved_curfr(event_info->MR_saved_regs);
        parent = MR_find_nth_ancestor(label_layout, 1, &base_sp, &base_curfr,
            &actual_level, &problem);
        if (actual_level == 1 && parent != NULL) {
            (void) MR_find_context(parent, &parent_filename, &parent_lineno);
        }
    }

    if (label_layout->MR_sll_port >= 0 &&
        (MR_TracePort) label_layout->MR_sll_port == MR_PORT_USER)
    {
        maybe_user_event_name =
            MR_user_event_spec(label_layout).MR_ues_event_name;
        fprintf(MR_mdb_out, " <%s>", maybe_user_event_name);
    } else {
        maybe_user_event_name = NULL;
    }

    MR_print_proc_id_trace_and_context(MR_mdb_out, MR_FALSE,
        MR_context_position, MR_user_event_context, label_layout->MR_sll_entry,
        maybe_user_event_name, base_sp, base_curfr,
        ( MR_print_goal_paths ? event_info->MR_event_path : "" ),
        filename, lineno, MR_port_is_interface(event_info->MR_trace_port),
        parent_filename, parent_lineno, indent);
}

static const MR_TraceCmdTableEntry  MR_trace_command_table[] =
{
    // The first two fields of this block should be the same
    // as in the file doc/mdb_command_list.

    { "forward", "step", MR_trace_cmd_step,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "goto", MR_trace_cmd_goto,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "next", MR_trace_cmd_next,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "finish", MR_trace_cmd_finish,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "fail", MR_trace_cmd_fail,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "exception", MR_trace_cmd_exception,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "return", MR_trace_cmd_return,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "user", MR_trace_cmd_user,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "forward", MR_trace_cmd_forward,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "mindepth", MR_trace_cmd_mindepth,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "maxdepth", MR_trace_cmd_maxdepth,
        MR_trace_movement_cmd_args, MR_trace_null_completer },
    { "forward", "continue", MR_trace_cmd_continue,
        MR_trace_movement_cmd_args, MR_trace_null_completer },

    { "backward", "retry", MR_trace_cmd_retry,
        MR_trace_retry_cmd_args, MR_trace_null_completer },

    { "browsing", "level", MR_trace_cmd_level,
        MR_trace_stack_cmd_args, MR_trace_null_completer },
    { "browsing", "up", MR_trace_cmd_up,
        MR_trace_stack_cmd_args, MR_trace_null_completer },
    { "browsing", "down", MR_trace_cmd_down,
        MR_trace_stack_cmd_args, MR_trace_null_completer },
    { "browsing", "vars", MR_trace_cmd_vars,
        NULL, MR_trace_null_completer },
    { "browsing", "held_vars", MR_trace_cmd_held_vars,
        NULL, MR_trace_null_completer },
    { "browsing", "print", MR_trace_cmd_print,
        MR_trace_print_cmd_args, MR_trace_var_completer },
    { "browsing", "browse", MR_trace_cmd_browse,
        MR_trace_print_cmd_args, MR_trace_var_completer },
    { "browsing", "stack", MR_trace_cmd_stack,
        MR_trace_stack_cmd_args, MR_trace_null_completer },
    { "browsing", "current", MR_trace_cmd_current,
        NULL, MR_trace_null_completer },
    { "browsing", "view", MR_trace_cmd_view,
        MR_trace_view_cmd_args, MR_trace_null_completer },
    { "browsing", "hold", MR_trace_cmd_hold,
        NULL, MR_trace_var_completer },
    { "browsing", "diff", MR_trace_cmd_diff,
        NULL, MR_trace_var_completer },
    { "browsing", "dump", MR_trace_cmd_dump,
        NULL, MR_trace_var_completer },
    { "browsing", "list", MR_trace_cmd_list,
        NULL, MR_trace_null_completer },

    { "breakpoint", "break", MR_trace_cmd_break,
        MR_trace_break_cmd_args, MR_trace_break_completer },
    { "breakpoint", "condition", MR_trace_cmd_condition,
        NULL, MR_trace_null_completer },
    { "breakpoint", "ignore", MR_trace_cmd_ignore,
        MR_trace_ignore_cmd_args, MR_trace_null_completer },
    { "breakpoint", "break_print", MR_trace_cmd_break_print,
        NULL, MR_trace_var_completer },
    { "breakpoint", "enable", MR_trace_cmd_enable,
        NULL, MR_trace_null_completer },
    { "breakpoint", "disable", MR_trace_cmd_disable,
        NULL, MR_trace_null_completer },
    { "breakpoint", "delete", MR_trace_cmd_delete,
        NULL, MR_trace_null_completer },
    { "breakpoint", "register", MR_trace_cmd_register,
        NULL, MR_trace_null_completer },
    { "breakpoint", "modules", MR_trace_cmd_modules,
        NULL, MR_trace_null_completer },
    { "breakpoint", "procedures", MR_trace_cmd_procedures,
        NULL, MR_trace_module_completer },

    // XXX For queries we should complete on all modules, not just those
    // that were compiled with tracing enabled.

    { "queries", "query", MR_trace_cmd_query,
        NULL, MR_trace_module_completer },
    { "queries", "cc_query", MR_trace_cmd_cc_query,
        NULL, MR_trace_module_completer },
    { "queries", "io_query", MR_trace_cmd_io_query,
        NULL, MR_trace_module_completer },

    { "table_io", "table_io", MR_trace_cmd_table_io,
        MR_trace_table_io_cmd_args, MR_trace_null_completer },

    { "parameter", "mmc_options", MR_trace_cmd_mmc_options,
        NULL, MR_trace_null_completer },
    { "parameter", "printlevel", MR_trace_cmd_printlevel,
        MR_trace_printlevel_cmd_args, MR_trace_null_completer },
    { "parameter", "scroll", MR_trace_cmd_scroll,
        MR_trace_on_off_args, MR_trace_null_completer },
    { "parameter", "stack_default_limit", MR_trace_cmd_stack_default_limit,
        NULL, MR_trace_null_completer },
    { "parameter", "goal_paths", MR_trace_cmd_goal_paths,
        MR_trace_on_off_args, MR_trace_null_completer },
    { "parameter", "scope", MR_trace_cmd_scope,
        MR_trace_scope_cmd_args, MR_trace_null_completer },
    { "parameter", "echo", MR_trace_cmd_echo,
        MR_trace_on_off_args, MR_trace_null_completer },
    { "parameter", "context", MR_trace_cmd_context,
        MR_trace_context_cmd_args, MR_trace_null_completer },
    { "parameter", "user_event_context", MR_trace_cmd_user_event_context,
        MR_trace_user_event_context_cmd_args, MR_trace_null_completer },
    { "parameter", "list_context_lines", MR_trace_cmd_list_context_lines,
        NULL, MR_trace_null_completer },
    { "parameter", "list_path", MR_trace_cmd_list_path,
        NULL, MR_trace_null_completer },
    { "parameter", "push_list_dir", MR_trace_cmd_push_list_dir,
        NULL, MR_trace_null_completer },
    { "parameter", "pop_list_dir", MR_trace_cmd_pop_list_dir,
        NULL, MR_trace_null_completer },
    { "parameter", "list_cmd", MR_trace_cmd_list_cmd,
        NULL, MR_trace_null_completer },
    { "parameter", "fail_trace_counts", MR_trace_cmd_fail_trace_counts,
        NULL, MR_trace_filename_completer },
    { "parameter", "pass_trace_counts", MR_trace_cmd_pass_trace_counts,
        NULL, MR_trace_filename_completer },
    { "parameter", "max_io_actions", MR_trace_cmd_max_io_actions,
        NULL, MR_trace_null_completer },
    { "parameter", "web_browser_cmd", MR_trace_cmd_web_browser_cmd,
        NULL, MR_trace_null_completer },
    { "parameter", "format", MR_trace_cmd_format,
        MR_trace_format_cmd_args, MR_trace_null_completer },
    { "parameter", "format_param", MR_trace_cmd_format_param,
        MR_trace_format_param_cmd_args, MR_trace_null_completer },
    { "parameter", "alias", MR_trace_cmd_alias,
        NULL, MR_trace_command_completer },
    { "parameter", "unalias", MR_trace_cmd_unalias,
        NULL, MR_trace_alias_completer },

    { "help", "document_category", MR_trace_cmd_document_category,
        NULL, MR_trace_null_completer },
    { "help", "document", MR_trace_cmd_document,
        NULL, MR_trace_null_completer },
    { "help", "help", MR_trace_cmd_help,
        NULL, MR_trace_help_completer },

    { "dd", "dd", MR_trace_cmd_dd,
        MR_trace_dd_cmd_args, MR_trace_null_completer },
    { "dd", "trust", MR_trace_cmd_trust,
        NULL, MR_trace_proc_spec_completer },
    { "dd", "untrust", MR_trace_cmd_untrust,
        NULL, MR_trace_null_completer },
    { "dd", "trusted", MR_trace_cmd_trusted,
        NULL, MR_trace_null_completer },

    { "misc", "source", MR_trace_cmd_source,
        MR_trace_source_cmd_args, MR_trace_filename_completer },
    { "misc", "save", MR_trace_cmd_save,
        NULL, MR_trace_filename_completer },
    { "misc", "quit", MR_trace_cmd_quit,
        MR_trace_quit_cmd_args, MR_trace_null_completer },
    { "misc", "shell", MR_trace_cmd_shell,
        NULL, MR_trace_null_completer },

    { "exp", "histogram_all", MR_trace_cmd_histogram_all,
        NULL, MR_trace_filename_completer },
    { "exp", "histogram_exp", MR_trace_cmd_histogram_exp,
        NULL, MR_trace_filename_completer },
    { "exp", "clear_histogram", MR_trace_cmd_clear_histogram,
        NULL, MR_trace_null_completer },
    { "exp", "dice", MR_trace_cmd_dice,
        NULL, MR_trace_null_completer },

    { "developer", "var_details", MR_trace_cmd_var_details,
        NULL, MR_trace_null_completer },
    { "developer", "term_size", MR_trace_cmd_term_size,
        NULL, MR_trace_null_completer },
    { "developer", "flag", MR_trace_cmd_flag,
        NULL, MR_trace_null_completer },
    { "developer", "subgoal", MR_trace_cmd_subgoal,
        NULL, MR_trace_null_completer },
    { "developer", "consumer", MR_trace_cmd_consumer,
        NULL, MR_trace_null_completer },
    { "developer", "gen_stack", MR_trace_cmd_gen_stack,
        NULL, MR_trace_null_completer },
    { "developer", "cut_stack", MR_trace_cmd_cut_stack,
        NULL, MR_trace_null_completer },
    { "developer", "pneg_stack", MR_trace_cmd_pneg_stack,
        NULL, MR_trace_null_completer },
    { "developer", "mm_stacks", MR_trace_cmd_mm_stacks,
        NULL, MR_trace_null_completer },
    { "developer", "nondet_stack", MR_trace_cmd_nondet_stack,
        MR_trace_nondet_stack_cmd_args, MR_trace_null_completer },
    { "developer", "stack_regs", MR_trace_cmd_stack_regs,
        NULL, MR_trace_null_completer },
    { "developer", "all_regs", MR_trace_cmd_all_regs,
        NULL, MR_trace_null_completer },
    { "developer", "debug_vars", MR_trace_cmd_debug_vars,
        NULL, MR_trace_null_completer },
    { "developer", "stats", MR_trace_cmd_stats,
        MR_trace_stats_cmd_args, MR_trace_filename_completer },
    { "developer", "print_optionals", MR_trace_cmd_print_optionals,
        MR_trace_on_off_args, MR_trace_null_completer },
    { "developer", "unhide_events", MR_trace_cmd_unhide_events,
        MR_trace_on_off_args, MR_trace_null_completer },
    { "developer", "table", MR_trace_cmd_table,
        NULL, MR_trace_proc_spec_completer },
    { "developer", "type_ctor", MR_trace_cmd_type_ctor,
        NULL, MR_trace_null_completer },
    { "developer", "class_decl", MR_trace_cmd_class_decl,
        NULL, MR_trace_null_completer },
    { "developer", "all_type_ctors", MR_trace_cmd_all_type_ctors,
        NULL, MR_trace_null_completer },
    { "developer", "all_class_decls", MR_trace_cmd_all_class_decls,
        NULL, MR_trace_null_completer },
    { "developer", "all_procedures", MR_trace_cmd_all_procedures,
        NULL, MR_trace_filename_completer },
    { "developer", "ambiguity", MR_trace_cmd_ambiguity,
        NULL, MR_trace_filename_completer },
    { "developer", "trail_details", MR_trace_cmd_trail_details,
        NULL, MR_trace_filename_completer },

    // End of doc/mdb_command_list.
    { NULL, "NUMBER", NULL,
        NULL, MR_trace_null_completer },
    { NULL, "EMPTY", NULL,
        NULL, MR_trace_null_completer },
    { NULL, NULL, NULL,
        NULL, MR_trace_null_completer },
};

MR_bool
MR_trace_command_completion_info(const char *word,
    MR_MakeCompleter *completer, const char *const **fixed_args)
{
    const MR_TraceCmdTableEntry *command_info;

    command_info = MR_trace_valid_command(word);
    if (command_info == NULL) {
        return MR_FALSE;
    } else {
        *completer = command_info->MR_cmd_arg_completer;
        *fixed_args = command_info->MR_cmd_arg_strings;
        return MR_TRUE;
    }
}

const MR_TraceCmdTableEntry *
MR_trace_valid_command(const char *word)
{
    int i;

    for (i = 0; MR_trace_command_table[i].MR_cmd_name != NULL; i++) {
        if (MR_streq(MR_trace_command_table[i].MR_cmd_name, word)) {
            return &MR_trace_command_table[i];
        }
    }

    return NULL;
}

MR_CompleterList *
MR_trace_command_completer(const char *word, size_t word_len)
{
    return MR_new_completer_elem(&MR_trace_command_completer_next,
        (MR_CompleterData) 0, MR_trace_no_free);
}

static char *
MR_trace_command_completer_next(const char *word, size_t word_len,
    MR_CompleterData *data)
{
    MR_Integer command_index;

    command_index = (MR_Integer) *data;
    while (1) {
        const char *command;
        const char *category;

        category = MR_trace_command_table[command_index].MR_cmd_category;
        command = MR_trace_command_table[command_index].MR_cmd_name;
        command_index++;
        *data = (void *) command_index;

        // We don't complete on the "EMPTY" and "NUMBER" entries
        // in the list of commands (they have a category entry of NULL).

        if (command == NULL) {
            return NULL;
        } else if (category != NULL && MR_strneq(word, command, word_len)) {
            return MR_copy_string(command);
        }
    }
}

void
MR_trace_interrupt_message(void)
{
    fprintf(MR_mdb_out, "\nmdb: got interrupt signal\n");
}
