/* -*-c-*- -------------- mixvm_command.c :
 * Implementation of the functions declared in mixvm_command.h
 * ------------------------------------------------------------------
 * Copyright (C) 2000 jose antonio ortega ruiz <jaortega@acm.org>
 *  
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *  
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *  
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *  
 */


#include <mixlib/mix.h>

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <readline/readline.h>
#include <readline/history.h>

#include <mixlib/mix_vm.h>
#include <mixlib/mix_vm_dump.h>
#include <mixlib/mix_eval.h>
#include "mixvm_command.h"

/* The names of functions that actually do the manipulation. */
#define DEC_FUN(name) \
static int cmd_##name (char *arg)

DEC_FUN (help_);
DEC_FUN (shell_);
DEC_FUN (load_);
DEC_FUN (run_);
DEC_FUN (next_);
DEC_FUN (pc_);
DEC_FUN (psym_);
DEC_FUN (preg_);
DEC_FUN (pflags_);
DEC_FUN (pall_);
DEC_FUN (pmem_);
DEC_FUN (sreg_);
DEC_FUN (scmp_);
DEC_FUN (sover_);
DEC_FUN (smem_);
DEC_FUN (ssym_);
DEC_FUN (sbp_);
DEC_FUN (sbpa_);
DEC_FUN (cbp_);
DEC_FUN (cbpa_);
DEC_FUN (cabp_);
DEC_FUN (compile_);
DEC_FUN (edit_);
DEC_FUN (weval_);
DEC_FUN (w2d_);
DEC_FUN (tron_);
DEC_FUN (troff_);
DEC_FUN (quit_);

/* A structure which contains information on the commands this program
   can understand. */
typedef struct {
  const char *name;		/* User printable name of the function. */
  Function *func;		/* Function to call to do the job. */
  const char *doc;		/* Documentation for this function.  */
  const char *usage;		/* Usage */
} COMMAND;
     
COMMAND commands[] = {
  { "help", cmd_help_, N_("Display this text"), "help [COMMAND]" },
  { "?", cmd_help_, N_("Synonym for `help'"), "? [COMMAND]" },
  { "!", cmd_shell_, N_("Execute shell command"), "! COMMAND" },
  { "load", cmd_load_, N_("Load a MIX code file"), "load FILENAME" },
  { "run", cmd_run_, N_("Run loaded or given MIX code file"), 
    "run [FILENAME]" },
  { "next", cmd_next_, N_("Execute next instruction(s)"), 
    "next [NO_OF_INS]"},
  { "pc", cmd_pc_, N_("Print program counter value"), "pc" },
  { "psym", cmd_psym_, N_("Print symbol value"), "psym [SYMBOLNAME]" },
  { "preg", cmd_preg_, N_("Print register value"), 
    "preg [A | X | J | I[1-6]]" },
  { "pflags", cmd_pflags_, N_("Print comparison and overflow flags"), 
    "pflags" },
  { "pall", cmd_pall_, N_("Print all registers and flags"), "pall" },
  { "pmem", cmd_pmem_, N_("Print memory contents in address range"), 
    "pmem FROM[-TO]" },
  { "sreg", cmd_sreg_, N_("Set register value"), 
    "preg A | X | J | I[1-6] VALUE" },
  { "scmp", cmd_scmp_, N_("Set comparison flag value"), "scmp L | E | G" },
  { "sover", cmd_sover_, N_("Set overflow flag value"), "sover T | F" },
  { "smem", cmd_smem_, N_("Set memory contents in given address"), 
    "smem ADDRESS VALUE" },
  { "ssym", cmd_ssym_, N_("Set a symbol\'s value"), "ssym SYMBOL WEXPR" },
  { "sbp", cmd_sbp_, N_("Set break point at given line"), "sbp LINENO" },
  { "cbp", cmd_cbp_, N_("Clear break point at given line"), "cbp LINENO" },
  { "sbpa", cmd_sbpa_, N_("Set break point at given address"),
    "sbpa ADDRESS" },
  { "cbpa", cmd_cbpa_, N_("Clear break point at given address"),
    "cbpa ADDRESS" },
  { "cabp", cmd_cabp_, N_("Clear all breakpoints"), "cabp" },
  { "compile", cmd_compile_, N_("Compile a source file"), "compile FILENAME"},
  { "edit", cmd_edit_, N_("Edit a source file"), "edit FILENAME"},
  { "weval", cmd_weval_, N_("Evaluate a given W-expres sion"), "weval WEXPR"},
  { "w2d", cmd_w2d_, N_("Convert a MIX word to its decimal value"),
    "w2d WORD" },
  { "tron", cmd_tron_, N_("Turn on instruction tracing."), "tron" },
  { "troff", cmd_troff_, N_("Turn off instruction tracing."), "troff" },
  { "quit", cmd_quit_, N_("Quit the program"), "quit" },
  { (char *)NULL, (Function *)NULL, (char *)NULL }
};


/* readline functions */
static char *
mixvm_cmd_generator_ (char *text, int state);


/* Attempt to complete on the contents of TEXT.  START and END bound the
   region of rl_line_buffer that contains the word to complete.  TEXT is
   the word to complete.  We can use the entire contents of rl_line_buffer
   in case we want to do some simple parsing.  Return the array of matches,
   or NULL if there aren't any. */
static char **
mixvm_cmd_completion_ (char *text, int start, int end)
{
  char **matches;
     
  matches = (char **)NULL;
     
  /* If this word is at the start of the line, then it is a command
     to complete.  Otherwise it is the name of a file in the current
     directory. */
  if (start == 0)
    matches = completion_matches (text, mixvm_cmd_generator_);
  
  return (matches);
}
     
/* Generator function for command completion.  STATE lets us know whether
   to start from scratch; without any state (i.e. STATE == 0), then we
   start at the top of the list. */
static char *
mixvm_cmd_generator_ (char *text, int state)
{
  static int list_index, len;
  const char *name;
     
  /* If this is a new word to complete, initialize now.  This includes
     saving the length of TEXT for efficiency, and initializing the index
     variable to 0. */
  if (!state)
    {
      list_index = 0;
      len = strlen (text);
    }
     
  /* Return the next name which partially matches from the command list. */
  while ( (name = commands[list_index].name) != NULL)
    {
      list_index++;
     
      if (strncmp (name, text, len) == 0)
	return (g_strdup (name));
    }
     
  /* If no names matched, then return NULL. */
  return ((char *)NULL);
}


/* command functions */
static COMMAND *
find_command_ (const char *name)
{
  int i;
     
  for (i = 0; commands[i].name; i++)
    if (strcmp (name, commands[i].name) == 0)
      return (&commands[i]);
     
  return ((COMMAND *)NULL);
}


/* the virtual machine and dump context */
static mix_vm_t *vm_ = NULL;
static mix_dump_context_t *dc_ = NULL;
/* the w-expression evaluator */
static mix_eval_t *eval_ = NULL;
/* trace status */
static gboolean is_tracing_ = FALSE;
/* uptime */
static mix_time_t uptime_ = 0;
static mix_time_t prog_time_ = 0;


static int
cmd_help_ (char *arg)
{
  static const int NO_OF_COLS = 6;
  
  int i;
  int printed = 0;
     
  for (i = 0; commands[i].name; i++)
    {
      if (!*arg || (strcmp (arg, commands[i].name) == 0))
	{
	  printf (_("%s\t\t%s. Usage: %s\n"), commands[i].name, 
		  _(commands[i].doc), commands[i].usage);
	  printed++;
	}
    }
     
  if (!printed)
    {
      printf (_("No commands match `%s'.  Possibilities are:\n"), arg);
     
      for (i = 0; commands[i].name; i++)
	{
	  if (printed == NO_OF_COLS)
	    {
	      printed = 0;
	      printf ("\n");
	    }
     
	  printf ("%s\t", commands[i].name);
	  printed++;
	}
     
      if (printed)
	printf ("\n");
    }
  return TRUE;
}

static int
cmd_load_ (char *arg)
{
  errno = 0;
  if (arg == NULL || *arg == '\0')
    {
      fputs (_("Missing file name\n"), stderr);
      return TRUE;
    }
  mix_eval_remove_symbols_from_table (eval_,
				      mix_vm_get_symbol_table (vm_));
  if (!mix_vm_load_file (vm_, arg) )
    {
      fprintf (stderr, _("Cannot load %s: "), arg);
      if ( errno == 0 )
	fputs (_("Wrong file format\n"), stderr);
      else
	perror (NULL);
      return TRUE + 1;
    }
  mix_eval_set_symbols_from_table (eval_,
				   mix_vm_get_symbol_table (vm_));

  fprintf (stderr, _("Program loaded. Start address: %d\n"),
	   mix_vm_get_prog_count (vm_));
  
  prog_time_ = 0;
  
  return TRUE;
}

/* trace current instruction */
static void
trace_ (void)
{
  enum {BUFFER_LEN = 128};
  static gchar STRINS[BUFFER_LEN];
  
  const mix_src_file_t *file = mix_vm_get_src_file (vm_);
  const gchar *line = "\n";
  mix_address_t loc = mix_vm_get_prog_count (vm_);
  mix_word_t ins = mix_vm_get_addr_contents (vm_, loc);
  mix_ins_t fins;
  mix_word_to_ins_uncheck (ins, fins);
  mix_ins_to_string_in_buffer (&fins, STRINS, BUFFER_LEN);
  
  if (file != NULL)
    {
      gulong b = mix_vm_get_break_lineno (vm_);
      if (b > 0) line = mix_src_file_get_line (file, b);
    }
  
  printf ("%d: [%s]\t%s", (gint)loc, STRINS, line);
}

static int
run_and_trace_ (void)
{
  int k = MIX_VM_OK;
  if (!is_tracing_)
    return mix_vm_run (vm_);
  else while (k == MIX_VM_OK)
    {
      trace_ ();
      k = mix_vm_exec_next (vm_);
    }
  return k;
}

static void
print_time_ (void)
{
  mix_time_t lapse = mix_vm_get_uptime(vm_) - uptime_;
  uptime_ += lapse;
  prog_time_ += lapse;
  printf(_("Elapsed time: %ld /Total program time: %ld (Total uptime: %ld)\n"),
	 lapse, prog_time_, uptime_);
}

static int
cmd_run_ (char *arg)
{
  if (arg != NULL && *arg != '\0' && cmd_load_ (arg) != TRUE) 
      return TRUE;
  puts (_("Running ..."));
  
  if (mix_vm_is_halted (vm_))
    {
      mix_vm_reset_program (vm_);
      prog_time_ = 0;
    }
  
  switch (run_and_trace_ ())
    {
    case MIX_VM_HALT:
      puts (_("... done"));
      break;
    case MIX_VM_BREAK:
      {
	gulong line = mix_vm_get_break_lineno (vm_);
	if (line != 0)
	  printf (_("... stopped: breakpoint at line %ld (address %d)\n"), 
		  line, mix_vm_get_prog_count (vm_));
	else
	  printf (_("... stopped: breakpoint at address %d\n"),
		  mix_vm_get_prog_count (vm_));
      }
      break;
    case MIX_VM_ERROR:
      puts (_("... error executing loaded file"));
      break;
    default:
      g_assert_not_reached ();
      break;
    }
  print_time_ ();
  return TRUE;
}

static int
cmd_next_ (char *arg)
{
  int ins_no = 1;
  int k;

  if ( strlen (arg) != 0 )
    {
      int k = 0;
      while (isdigit (arg[k]))
	k++;
      if (arg[k] != '\0')
	{
	  fprintf (stderr, _("Invalid argument: %s\n"), arg);
	  return cmd_help_ ("next");
	}
      ins_no = atoi (arg);
    }

  if (mix_vm_is_halted (vm_))
    {
      mix_vm_reset_program (vm_);
      prog_time_ = 0;
    }
  
  while ( ins_no-- > 0 )
    {
      if (is_tracing_) trace_ ();
      k = mix_vm_exec_next (vm_);
      if (k == MIX_VM_HALT)
	{
	  fprintf (stderr, _("End of program reached at address %d\n"),
		   mix_vm_get_prog_count (vm_));
	  break;
	}
      else if (k == MIX_VM_ERROR)
	{
	  fprintf (stderr, _("Error at address %d\n"),
		   mix_vm_get_prog_count (vm_));
	  break;
	}
    }
  print_time_ ();
  return TRUE;
}

static int
cmd_quit_ (char *arg)
{
  puts ("Quitting ...");
  if ( vm_ ) mix_vm_delete (vm_);
  if ( dc_ ) mix_dump_context_delete (dc_);
  return FALSE;
}

static int
cmd_pc_ (char *arg)
{
  printf ("Current address: %d\n", mix_vm_get_prog_count (vm_));
  return TRUE;
}

static int
cmd_psym_ (char *arg)
{
  const mix_symbol_table_t *table = mix_eval_symbol_table (eval_);
  if ( table == NULL )
    fputs (_("Symbol table not available\n"), stderr);
  else if (arg != NULL && *arg != '\0')
    {
      if ( mix_symbol_table_is_defined (table, arg) )
	{
	  mix_word_print (mix_symbol_table_value (table, arg), NULL);
	  putc ('\n', stdout);
	}
      else
	printf (_("%s: symbol not defined\n"), arg);
    }
  else
    mix_symbol_table_print (table, MIX_SYM_ROWS, stdout, TRUE);
      
  return TRUE;
}

static int
cmd_preg_ (char *arg)
{
  mix_dump_context_set_opt (dc_, MIX_DUMP_NONE);
  if ( strlen (arg) == 0 )
    mix_dump_context_add_opt (dc_, MIX_DUMP_rALL);
  else switch (*arg)
    {
    case 'A':
      mix_dump_context_add_opt (dc_, MIX_DUMP_rA);
      break;
    case 'X':
      mix_dump_context_add_opt (dc_, MIX_DUMP_rX);
      break;
    case 'J':
      mix_dump_context_add_opt (dc_, MIX_DUMP_rJ);
      break;
    case 'I':
      {
	if ( strlen (arg) == 1 )
	  mix_dump_context_add_opt (dc_, MIX_DUMP_rIa);
	else
	  {
	    static gint32 opt[] = { MIX_DUMP_rI1, MIX_DUMP_rI2,
				    MIX_DUMP_rI3, MIX_DUMP_rI4,
				    MIX_DUMP_rI5, MIX_DUMP_rI6 
	    };
	    int i = arg[1] - '1';
	    if ( i < 0 || i > 5 )
	      {
		fprintf (stderr, _("Invalid I index: %d"), i);
		return TRUE;
	      }
	    mix_dump_context_add_opt (dc_, opt[i]);
	  }
      }
      break;
    default:
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      return TRUE;
    }
  mix_vm_dump (vm_, dc_);
  return TRUE;
}

static int
cmd_pflags_ (char *arg)
{
  mix_dump_context_set_opt (dc_, MIX_DUMP_CMP | MIX_DUMP_OVER);
  mix_vm_dump (vm_, dc_);
  return TRUE;
}

static int
cmd_pall_ (char *arg)
{
  mix_dump_context_set_opt (dc_, MIX_DUMP_ALL_NOMEM);
  mix_vm_dump (vm_, dc_);
  return TRUE;
}

static int
cmd_pmem_ (char *arg)
{
  glong begin = MIX_SHORT_ZERO, end = MIX_SHORT_ZERO;
  int i = 0;
  gboolean error = FALSE;
  
  if ( strlen (arg) == 0 )
    {
      fputs (_("Missing memory address\n"), stderr);
      return TRUE;
    }
  while (isdigit (arg[i]))
    i++;
  while (isspace (arg[i]))
    i++;
  if (arg[i] == '\0')
      begin = end = atol (arg);
  else if (arg[i] == '-')
    {
      arg[i++] = '\0';
      begin = atol (arg);
      arg = arg + i;
      i = 0;
      while (isdigit (arg[i]))
	i++;
      while (isspace (arg[i]))
	i++;
      if (arg[i] != '\0') 
	error = TRUE;
      else 
	end = atol (arg);
    }
  else
    error = TRUE;
  
  if (error)
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      return cmd_help_("pmem");
    }
  if ( end < begin || end > MIX_VM_CELL_NO - 1 )
    {
      fprintf (stderr, _("Invalid range: %ld-%ld\n"), begin, end);
      return TRUE;
    }

  mix_dump_context_set_opt (dc_, MIX_DUMP_CELLS);
  mix_dump_context_range (dc_, mix_short_new (begin), mix_short_new (end + 1));
  mix_vm_dump (vm_, dc_);
  
  return TRUE;
}

static int
cmd_sreg_ (char *arg)
{
  int i = 0;
  char reg = arg[0];
  gboolean ok = TRUE;
  long value;
  
  i = (reg == 'I') ? 2 : 1;
  ok = strlen (arg) > 2 && isspace (arg[i]);
  if (ok)
    {
      while (isspace (arg[i])) i++;
      ok = isdigit (arg[i]) || arg[i] == '+' || arg[i] == '-';
      if (ok)
	{
	  value = atol (arg + i);
	  if (arg[i] == '+' || arg[i] == '-') i++;
	  while (isdigit (arg[i])) i++;
	  ok = (arg[i] == '\0');
	  if (ok)
	    switch (reg)
	      {
	      case 'A':
		mix_vm_set_rA (vm_, mix_word_new (value));
		break;
	      case 'X':
		mix_vm_set_rX (vm_, mix_word_new (value));
		break;
	      case 'J':
		if ( value >= 0 )
		  mix_vm_set_rJ (vm_, mix_short_new (value));
		else
		  ok = FALSE;
		break;
	      case 'I':
		{
		  guint k = arg[1] - '0';
		  if ( k < 7 ) 
		    mix_vm_set_rI (vm_, k, mix_short_new (value));
		  else
		    ok = FALSE;
		}
		break;
	      default:
		ok = FALSE;
	      }
	}
    }
  if (!ok)
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      cmd_help_ ("sreg");
    }
    
  return TRUE;
}

static int
cmd_scmp_ (char *arg)
{
  gboolean ok = (strlen (arg) == 1);
  if (ok) switch (arg[0])
    {
    case 'L':
      mix_vm_set_cmpflag (vm_, mix_LESS);
      break;
    case 'E':
      mix_vm_set_cmpflag (vm_, mix_EQ);
      break;
    case 'G':
      mix_vm_set_cmpflag (vm_, mix_GREAT);
      break;
    default:
      ok = FALSE;
    }
  if (!ok)
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      cmd_help_ ("scmp");
    }
  
  return TRUE;
}

static int
cmd_sover_ (char *arg)
{
  gboolean ok = (strlen (arg) == 1);
  if (ok) switch (arg[0])
    {
    case 'T':
      mix_vm_set_overflow (vm_, TRUE);
      break;
    case 'F':
      mix_vm_set_overflow (vm_, FALSE);
      break;
    default:
      ok = FALSE;
    }
  if (!ok)
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      cmd_help_ ("sover");
    }
  
  return TRUE;
}

static int
cmd_smem_ (char *arg)
{
  gboolean ok = (strlen (arg) > 2 && isdigit (arg[0]));
  gulong addr;
  glong value;
  int k = 0;
  
  if (ok)
    {
      while (isdigit (arg[k])) k++;
      ok = isspace (arg[k]);
      if (ok)
	{
	  arg[k++] = '\0';
	  addr = atol (arg);
	  ok = addr < MIX_VM_CELL_NO;
	}
      if (ok)
	{
	  while (isspace (arg[k])) k++;
	  value = atol (arg + k);
	  if ( arg[k] == '+' || arg[k] == '-' ) k++;
	  while (isdigit (arg[k])) k++;
	  ok = arg[k] == '\0';
	}
    }

  if (ok)
    mix_vm_set_addr_contents (vm_, mix_short_new (addr), mix_word_new (value));
  else
    {
      fprintf (stderr, "Invalid argument: %s\n", arg);
      cmd_help_ ("smem");
    }
  
  return TRUE;
}

static int
cmd_ssym_ (char *arg)
{
  if (arg == NULL || strlen(arg) == 0)
    {
      fprintf (stderr, _("Missing arguments\n"));
      return cmd_help_ ("ssym");
    }
  else
    {
      gchar *a = g_strdup (arg);
      gchar *s = strtok (a, " \t");
      gchar *w = strtok (NULL, " \t");
      if (w != NULL && strtok (NULL, " \t") == NULL)
	{
	  cmd_weval_ (w);
	  if (mix_eval_last_error (eval_) == MIX_EVAL_OK)
	    mix_eval_set_symbol (eval_, s, mix_eval_value (eval_));
	}
      else
	{
	  fprintf (stderr, _("Wrong argument number\n"));
	  cmd_help_ ("ssym");
	}
      g_free (a);
      return TRUE;
    }
}

static int
cmd_sbp_ (char *arg)
{
  glong lineno;
  glong k = 0;
  while (isdigit (arg[k])) k++;
  if (arg[k] != '\0')
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      return cmd_help_ ("sbp");
    }
  lineno = atol (arg);
  switch (k = mix_vm_set_breakpoint (vm_, lineno))
    {
    case MIX_VM_BP_INV_LINE:
      fprintf (stderr, _("Line number %ld too high\n"), lineno);
      break;
    case MIX_VM_BP_ERROR:
      fputs (_("Could not set breakpoint. Internal error\n"), stderr);
      break;
    case MIX_VM_BP_NDEBUG:
      fputs (_("Could not set breakpoint. No debug info available\n"), stderr);
      break;
    default:
      fprintf (stderr, _("Breakpoint set at line %ld\n"), k);
      break;
    }
  return TRUE;
}

static int
cmd_sbpa_ (char *arg)
{
  glong address;
  glong k = 0;
  while (isdigit (arg[k])) k++;
  if (arg[k] != '\0')
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      return cmd_help_ ("sbpa");
    }
  address = atol (arg);
  switch (mix_vm_set_breakpoint_address (vm_, address))
    {
    case MIX_VM_BP_INV_ADDRESS:
      fprintf (stderr, _("Invalid address %ld\n"), address);
      break;
    case MIX_VM_BP_ERROR:
      fputs (_("Could not set breakpoint. Internal error\n"), stderr);
      break;
    default:
      fprintf (stderr, _("Breakpoint set at address %ld\n"), address);
      break;
    }
  return TRUE;
}

static int
cmd_cbp_ (char *arg)
{
  glong lineno;
  int k = 0;
  while (isdigit (arg[k])) k++;
  if (arg[k] != '\0')
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      return cmd_help_ ("cbp");
    }
  lineno = atol (arg);
  switch (mix_vm_clear_breakpoint (vm_, lineno))
    {
    case MIX_VM_BP_INV_LINE:
      fprintf (stderr, _("No breakpoint set at line %ld\n"), lineno);
      break;
    case MIX_VM_BP_ERROR:
      fputs (_("Could not set breakpoint. Internal error\n"), stderr);
      break;
    case MIX_VM_BP_NDEBUG:
      fputs (_("No debug info available\n"), stderr);
      break;
    case MIX_VM_BP_OK:
      fprintf (stderr, _("Breakpoint cleared at line %ld\n"), lineno);
      break;
    default:
      g_assert_not_reached ();
      break;
    }
  return TRUE;
}

static int
cmd_cbpa_ (char *arg)
{
  glong address;
  glong k = 0;
  while (isdigit (arg[k])) k++;
  if (arg[k] != '\0')
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      return cmd_help_ ("cbpa");
    }
  address = atol (arg);
  switch (mix_vm_clear_breakpoint_address (vm_, address))
    {
    case MIX_VM_BP_INV_ADDRESS:
      fprintf (stderr, _("Invalid address %ld\n"), address);
      break;
    case MIX_VM_BP_ERROR:
      fputs (_("Could not clear breakpoint. Internal error\n"), stderr);
      break;
    default:
      fprintf (stderr, _("Breakpoint cleared at address %ld\n"), address);
      break;
    }
  return TRUE;
}


static int
cmd_cabp_ (char *arg)
{
  if (strlen (arg) != 0)
    {
      fprintf (stderr, _("Invalid argument: %s\n"), arg);
      return cmd_help_ ("cabp");
    }
  mix_vm_clear_all_breakpoints (vm_);
  return TRUE;
}

static int 
cmd_shell_ (char *arg)
{
  system (arg);
  return TRUE;
}

static int
cmd_compile_ (char *arg)
{
  gchar *line;
  
  if ( strlen (arg) == 0 )
    return cmd_help_ ("compile");
  
  line = g_strconcat ("mixasm -g ", arg, NULL);
  if ( system (line) == EXIT_SUCCESS )
    fputs (_("Successful compilation\n"), stderr);
  g_free (line);
  
  return TRUE;
}

static int
cmd_edit_ (char *arg)
{
  static const gchar * envars[] = { "MDK_EDITOR", "X_EDITOR", "EDITOR",
				    "VISUAL" 
  };
  static const guint s = sizeof (envars) / sizeof (envars[0]);
  static const gchar *editor = NULL;
  
  if ( strlen (arg) == 0 )
    return cmd_help_ ("edit");
  
  if (!editor)
    {
      int k;
      for (k = 0; k < s; k++)
	if ( (editor = getenv (envars[k])) != NULL ) break;
    }
  if (!editor)
    {
      int k;
      fputs (_("Cannot find editor ("), stderr);
      for (k = 0; k < s; k++)
	fprintf (stderr, "%s ", envars[k]);
      fputs (_("undefined)\n"), stderr);
    }
  else
    {
      gchar *line = g_strconcat (editor, " ", arg, NULL);
      system (line);
      g_free (line);
    }
  
  return TRUE;
}

static int
cmd_weval_ (char *arg)
{
  if ( strlen (arg) == 0 )
    return cmd_help_ ("weval");

  if (mix_eval_expression_with_loc (eval_, arg,
				    mix_vm_get_prog_count (vm_)) ==
      MIX_EVAL_OK)
    {
      mix_word_print (mix_eval_value (eval_), NULL);
      putc ('\n', stdout);
    }
  else
    {
      gint pos = mix_eval_last_error_pos (eval_);
      gint k, len = strlen (arg);
      g_assert(pos > -1 && pos <= len);
      for (k = 0; k<pos; ++k) fputc (arg[k], stderr);
      fputc ('\n', stderr);
      for (k = 0; k<pos; ++k) fputc (' ', stderr);
      for (k = pos; k < len; ++k) fputc (arg[k], stderr);
      fprintf (stderr, _("\nEvaluation error: %s\n"),
	       mix_eval_last_error_string (eval_));
    }
        
  return TRUE;
}

static int
cmd_w2d_ (char *arg)
{
  if ( strlen (arg) == 0 )
    return cmd_help_ ("w2d");
  else 
    {
      gchar *cp = g_strdup (arg), *a = cp;
      mix_byte_t bytes[5] = {0, 0, 0, 0, 0};
      gchar *b;
      guint k = 0;
      gboolean is_n = (a[0] == '-'), success = TRUE;
      if (a[0] == '+' || a[0] == '-') ++a;
      b = strtok (a, " \t");
      while (b != NULL && k < 5)
	{
	  if (strlen (b) != 2 || !isdigit(b[0]) || !isdigit(b[1]))
	    {
	      fprintf (stderr, _("Incorrect byte specification: %s\n"), b);
	      success = FALSE;
	      b = NULL;
	    }
	  else
	    {
	      bytes[k++] = mix_byte_new (atoi (b));
	      b = strtok (NULL, " \t");
	    }
	}
      if (success) 
	{
	  if (strtok (NULL, "\t") != NULL)
	    fprintf (stderr, _("The expression %s does not fit in a word\n"),
		     arg);
	  else
	    {
	      mix_word_t w = mix_bytes_to_word (bytes, k);
	      fprintf (stdout, "%s%ld\n", is_n? "-":"+",
		       mix_word_magnitude (w));
	    }
	}
      g_free (cp);
    }
  
  return TRUE;
}

static int
cmd_tron_ (char *arg)
{
  is_tracing_ = TRUE;
  printf (_("Instruction tracing has been turned ON.\n"));
  return TRUE;
}

static int
cmd_troff_ (char *arg)
{
  is_tracing_ = FALSE;
  printf (_("Instruction tracing has been turned OFF.\n"));
  return TRUE;
}


/* external interface */
void
mixvm_cmd_init (char *arg)
{
  /* Tell the completer that we want a crack first. */
  rl_attempted_completion_function = (CPPFunction *)mixvm_cmd_completion_;
  /* initialise the vm */
  vm_ = mix_vm_new ();
  dc_ = mix_dump_context_new (MIX_DUMP_DEF_CHANNEL, 
			      MIX_SHORT_ZERO, MIX_SHORT_ZERO,
			      MIX_DUMP_ALL);
  eval_ = mix_eval_new ();
  
  if ( vm_ == NULL || dc_ == NULL || eval_ == NULL)
    g_error (_("Failed initialisation (no memory resources)"));
  if (arg)
    cmd_load_ (arg);
}

gboolean
mixvm_cmd_exec (char *line)
{
  int i;
  COMMAND *command;
  char *word;

  if (!line) return cmd_quit_(NULL);
  
  /* strip white  space */
  line = g_strstrip(line);
   
  /* Isolate the command word. */
  i = 0;
  while (line[i] && isspace (line[i]))
    i++;
  word = line + i;
     
  while (line[i] && !isspace (line[i]))
    i++;
     
  if (line[i])
    line[i++] = '\0';
     
  if (word == NULL || strlen (word) == 0)
    return TRUE;
  
  command = find_command_ (word);
     
  if (!command)
    {
      fprintf (stderr, _("%s: No such command. Try \'help\'\n"), word);
      return TRUE;
    }
     
  /* Get argument to command, if any. */
  while (isspace (line[i]))
    i++;
     
  word = line + i;
     
  /* Call the function. */
  return ((*(command->func)) (word));
}