/* -*-c-*- -------------- mix_vm_command.c :
 * Implementation of the functions declared in mix_vm_command.h
 * ------------------------------------------------------------------
 *  Last change: Time-stamp: "2001-04-22 17:20:07 jao"
 * ------------------------------------------------------------------
 * Copyright (C) 2001 Free Software Foundation, Inc.
 *  
 * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "mix_device.h"
#include "mix_vm.h"
#include "mix_vm_dump.h"
#include "mix_eval.h"
#include "mix_predicate.h"
#include "mix_vm_command.h"

#ifdef HAVE_LIBHISTORY
#  include <readline/history.h>
#endif

#ifdef HAVE_LIBREADLINE
#  include <readline/readline.h>
#endif

/* hooks */
typedef struct 
{
  mix_vm_cmd_hook_t func;
  gpointer data;
} hook_;

typedef struct 
{
  mix_vm_cmd_global_hook_t func;
  gpointer data;
} global_hook_;

#define PRNO_ MIX_PRED_MEM

struct mix_vm_cmd_dispatcher_t 
{
  mix_vm_t *vm;			/* the virtual machine */
  gboolean result;		/* last command's outcome */
  gchar *program;		/* the name of the last loaded program */
  gchar *editor;		/* edit command line template */
  gchar *assembler;		/* compile command line template */
  FILE *out;			/* message output file */
  FILE *err;			/* error output file */
  mix_dump_context_t *dump;	/* dump context for output */
  mix_eval_t *eval;		/* evaluator for w-expressions */
  gboolean trace;		/* tracing flag */
  gboolean printtime;		/* printing times flag */
  mix_time_t uptime;		/* total running time */
  mix_time_t laptime;		/* last run time */
  mix_time_t progtime;		/* current program running time */
  hook_ pre_hooks[MIX_CMD_INVALID]; /* Pre-command hooks */
  hook_ post_hooks[MIX_CMD_INVALID]; /* Post-command hooks */
  global_hook_ global_pre;	/* global pre-command hook */
  global_hook_ global_post;	/* global post-command hook */
  mix_config_t *config;		/* externally provided configuration */
  mix_predicate_t *preds[PRNO_]; /* predicates for conditional breakpoints */
  GHashTable *mem_preds;	/* predicates for memory conditional bps */
};

/* command handlers */
typedef gboolean (*Function_)(mix_vm_cmd_dispatcher_t *, const gchar *);
#define DEC_FUN(name) \
  static int cmd_##name (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)

DEC_FUN (help_);
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 (weval_);
DEC_FUN (w2d_);
DEC_FUN (tracing_);
DEC_FUN (timing_);
DEC_FUN (edit_);
DEC_FUN (compile_);
DEC_FUN (pedit_);
DEC_FUN (sedit_);
DEC_FUN (pasm_);
DEC_FUN (sasm_);
DEC_FUN (devdir_);
DEC_FUN (sbpr_);
DEC_FUN (sbpm_);
DEC_FUN (sbpc_);
DEC_FUN (sbpo_);
DEC_FUN (cbpr_);
DEC_FUN (cbpm_);
DEC_FUN (cbpc_);
DEC_FUN (cbpo_);
DEC_FUN (pbt_);


/* internal command info struct */
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_;

/* available commands (in the same order as the type enum) */
     
command_ commands_[] = {
  { "help", cmd_help_, N_("Display this text"), "help [COMMAND]"},
  { "load", cmd_load_, N_("Load a MIX code file"), "load FILENAME"},
  { "edit", cmd_edit_, N_("Edit a MIXAL source file"), "edit [FILENAME]"},
  { "pedit", cmd_pedit_, N_("Print the external editor command"), "pedit"},
  { "sedit", cmd_sedit_, N_("Set the external editor command"),
    "sedit COMMAND (e.g. emacs %s)"},
  { "compile", cmd_compile_, N_("Compile a MIXAL source file"),
    "compile [FILENAME]"},
  { "pasm", cmd_pasm_, N_("Print the compile command"), "pasm"},
  { "sasm", cmd_sasm_, N_("Set the compile command"),
    "sasm COMMAND (e.g. mixasm -g -l %s)"},
  { "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"},
  { "sbpr", cmd_sbpr_, N_("Set conditional breakpoint on register change"),
    "sbpr A | X | J | I[1-6]"},
  { "cbpr", cmd_cbpr_, N_("Clear conditional breakpoint on register change"),
    "sbpr A | X | J | I[1-6]"},
  { "sbpm", cmd_sbpm_, N_("Set conditional breakpoint on mem cell change"),
    "sbpm ADDRESS"},
  { "cbpm", cmd_cbpm_, N_("Clear conditional breakpoint on mem cell change"),
    "cbpm ADDRESS"},
  { "sbpc", cmd_sbpc_,
    N_("Set conditional breakpoint on comparison flag change"), "sbpc"},
  { "cbpc", cmd_cbpc_,
    N_("Clear conditional breakpoint on comparison flag change"), "cbpc"},
  { "sbpo", cmd_sbpo_,
    N_("Set conditional breakpoint on overflow toggled"), "sbpo"},
  { "cbpo", cmd_cbpo_,
    N_("Set conditional breakpoint on overflow toggled"), "cbpo"},
  { "cabp", cmd_cabp_, N_("Clear all breakpoints"), "cabp"},
  { "weval", cmd_weval_, N_("Evaluate a given W-expression"), "weval WEXPR"},
  { "w2d", cmd_w2d_, N_("Convert a MIX word to its decimal value"),
    "w2d WORD"},
  { "tracing", cmd_tracing_, N_("Turn on/off instruction tracing"),
    "tracing [on|off]"},
  { "pbt", cmd_pbt_, N_("Print backtrace of executed instructions"),
    "pbt [INS_NO] (e.g pbt 5)"},
  { "timing", cmd_timing_, N_("Turn on/off timing statistics"),
    "timing [on|off]"},
  { "devdir", cmd_devdir_, N_("Print/set devices directory"),
    "devdir [NEWDIR]"},
  { NULL, NULL, NULL, NULL},
};

/* configuration keys */
static const gchar *TRACING_KEY_ = "Tracing";
static const gchar *TIMING_KEY_ = "Timing";
static const gchar *EDITOR_KEY_ = "Editor";
static const gchar *ASM_KEY_ = "Assembler";


/* external interface */

/* conversion from/to commands to strings */
const gchar *
mix_vm_command_to_string (mix_vm_command_t cmd)
{
  if (cmd < MIX_CMD_INVALID) return commands_[cmd].name;
  else return NULL;
}

mix_vm_command_t
mix_vm_command_from_string (const gchar *name)
{
  /* inefficient linear search, i know */
  gint cmd = 0;
  while (cmd < MIX_CMD_INVALID && strcmp (name, commands_[cmd].name))
    ++cmd;
  return cmd;
}

/* get help string about a command */
const gchar *
mix_vm_command_help (mix_vm_command_t cmd)
{
  if (cmd < MIX_CMD_INVALID) return commands_[cmd].doc;
  else return NULL;
}

const gchar *
mix_vm_command_usage (mix_vm_command_t cmd)
{
  if (cmd < MIX_CMD_INVALID) return commands_[cmd].usage;
  else return NULL;
}

/* create a new command dispatcher */
mix_vm_cmd_dispatcher_t *
mix_vm_cmd_dispatcher_new (FILE *out_fd, /* output messages file */
			   FILE *err_fd /* error messages file */)
{
  mix_vm_cmd_dispatcher_t *result = NULL;
  int k;
  
  /* g_return_val_if_fail (out_fd && err_fd, NULL); */
  
  result = g_new (mix_vm_cmd_dispatcher_t, 1);
  result->result = TRUE;
  result->out = out_fd;
  result->err = err_fd;
  result->uptime = result->laptime = result->progtime = 0;
  result->printtime = TRUE;
  result->trace = FALSE;
  result->program = NULL;
  result->editor = NULL;
  result->assembler = NULL;
  result->eval = mix_eval_new ();
  result->dump = mix_dump_context_new (out_fd, 
				       MIX_SHORT_ZERO, MIX_SHORT_ZERO,
				       MIX_DUMP_ALL);
  result->vm = mix_vm_new ();
  result->global_pre.func = result->global_post.func = NULL;
  result->global_pre.data = result->global_post.data = NULL;
  for (k =0; k < MIX_CMD_INVALID; ++k)
    {
      result->pre_hooks[k].func = result->post_hooks[k].func = NULL;
      result->pre_hooks[k].data = result->post_hooks[k].data = NULL;
    }
  result->config = NULL;
  
  for (k = 0; k < PRNO_; ++k)
    result->preds[k] = mix_predicate_new (k);
  
  result->mem_preds = g_hash_table_new (NULL, NULL);
  
  return result;
}

mix_vm_cmd_dispatcher_t *
mix_vm_cmd_dispatcher_new_with_config (FILE *out, FILE *err,
				       mix_config_t *config)
{
  mix_vm_cmd_dispatcher_t *result = mix_vm_cmd_dispatcher_new (out, err);
  if (result != NULL && (result->config = config) != NULL)
    {
      gint hsize = 0;
      const gchar *val = mix_config_get (result->config, TRACING_KEY_);
      if (val) cmd_tracing_ (result, val);
      val = mix_config_get (result->config, EDITOR_KEY_);
      if (val) mix_vm_cmd_dispatcher_set_editor (result, val);
      val = mix_config_get (result->config, ASM_KEY_);
      if (val) mix_vm_cmd_dispatcher_set_assembler (result, val);
      val = mix_config_get (result->config, TIMING_KEY_);
      if (val) cmd_timing_ (result, val);
      val = mix_config_get_devices_dir (result->config);
      if (!val || !mix_stat_dir (val, "devices"))
	{
	  gchar *dirname = g_dirname (mix_config_get_filename (config));
	  cmd_devdir_ (result, dirname);
	  g_free (dirname);
	}
      else
	mix_device_set_dir (val);
#ifdef HAVE_LIBHISTORY
      val = mix_config_get_history_file (result->config);
      hsize = mix_config_get_history_size (result->config);
      using_history ();
      stifle_history (hsize);
      if (val)
	{
	  read_history ((char *)val);
	  history_set_pos (history_base + history_length - 1);
	}
#endif
    }
  return result;
}
  

/* delete (does not close the fds in the constructor) */
static gboolean
del_pred_ (gpointer key, gpointer val, gpointer data)
{
  if (val) mix_predicate_delete ((mix_predicate_t *)val);
  return TRUE;
}

void
mix_vm_cmd_dispatcher_delete (mix_vm_cmd_dispatcher_t *dis)
{
  const gchar *hfile = NULL;
  gint k;
  
  g_return_if_fail (dis != NULL);
  mix_eval_delete (dis->eval);
  mix_dump_context_delete (dis->dump);
  mix_vm_delete (dis->vm);
  if (dis->editor) g_free (dis->editor);
  if (dis->editor) g_free (dis->assembler);
#ifdef HAVE_LIBHISTORY
  if (dis->config && (hfile = mix_config_get_history_file
		      (dis->config)))
    write_history ((char *)hfile);
#endif
  for (k = 0; k < PRNO_; ++k) mix_predicate_delete (dis->preds[k]);
  g_hash_table_foreach_remove (dis->mem_preds, del_pred_, NULL);
  g_hash_table_destroy (dis->mem_preds);
  g_free (dis);
}

/* set/get out/error streams */
FILE * /* old output stream */
mix_vm_cmd_dispatcher_set_out_stream (mix_vm_cmd_dispatcher_t *dis, FILE *out)
{
  FILE *old = NULL;
  g_return_val_if_fail (dis != NULL, old);
  old = dis->out;
  dis->out = out;
  dis->dump->channel = out;
  return old;
}

FILE * /* old error stream */
mix_vm_cmd_dispatcher_set_error_stream (mix_vm_cmd_dispatcher_t *dis,
					FILE *err)
{
  FILE *old = NULL;
  g_return_val_if_fail (dis != NULL, old);
  old = dis->err;
  dis->err = err;
  return old;
}


/* set editor and compiler templates */
void
mix_vm_cmd_dispatcher_set_editor (mix_vm_cmd_dispatcher_t *dis,
				  const gchar *edit_tplt)
{
  g_return_if_fail (dis != NULL);
  if (dis->editor) g_free (dis->editor);
  dis->editor = (edit_tplt) ? g_strdup (edit_tplt) : NULL;
  if (dis->config && dis->editor)
    mix_config_update (dis->config, EDITOR_KEY_, dis->editor);
}

void
mix_vm_cmd_dispatcher_set_assembler (mix_vm_cmd_dispatcher_t *dis,
				     const gchar *asm_tplt)
{
  g_return_if_fail (dis != NULL);
  if (dis->assembler) g_free (dis->assembler);
  dis->assembler = (asm_tplt) ? g_strdup (asm_tplt) : NULL;
  if (dis->config && dis->assembler)
    mix_config_update (dis->config, ASM_KEY_, dis->assembler);
}

const gchar *
mix_vm_cmd_dispatcher_get_editor (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, NULL);
  return dis->editor;
}

const gchar *
mix_vm_cmd_dispatcher_get_assembler (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, NULL);
  return dis->assembler;
}

const gchar *
mix_vm_cmd_dispatcher_get_src_file_path (const mix_vm_cmd_dispatcher_t *dis)
{
  static gchar *PATH = NULL;
  
  const mix_vm_t *vm = mix_vm_cmd_dispatcher_get_vm (dis);
  const mix_src_file_t *f  = mix_vm_get_src_file (vm);
  
  if (PATH)
    {
      g_free (PATH);
      PATH = NULL;
    }

  if (f)
    PATH = mix_file_complete_name (mix_src_file_get_path (f), MIX_SRC_DEFEXT);
      
  return PATH;
}

/* install hooks */
void
mix_vm_cmd_dispatcher_pre_hook (mix_vm_cmd_dispatcher_t *dis,
				mix_vm_command_t cmd,
				mix_vm_cmd_hook_t hook, gpointer data)
{
  g_return_if_fail (dis != NULL);
  g_return_if_fail (cmd < MIX_CMD_INVALID);
  dis->pre_hooks[cmd].func = hook;
  dis->pre_hooks[cmd].data = data;
}

void
mix_vm_cmd_dispatcher_post_hook (mix_vm_cmd_dispatcher_t *dis,
				 mix_vm_command_t cmd,
				 mix_vm_cmd_hook_t hook, gpointer data)
{
  g_return_if_fail (dis != NULL);
  g_return_if_fail (cmd < MIX_CMD_INVALID);
  dis->post_hooks[cmd].func = hook;
  dis->post_hooks[cmd].data = data;
}

void
mix_vm_cmd_dispatcher_global_pre_hook (mix_vm_cmd_dispatcher_t *dis,
				       mix_vm_cmd_global_hook_t hook,
				       gpointer data)
{
  g_return_if_fail (dis != NULL);
  dis->global_pre.func = hook;
  dis->global_pre.data = data;
}

void
mix_vm_cmd_dispatcher_global_post_hook (mix_vm_cmd_dispatcher_t *dis,
					mix_vm_cmd_global_hook_t hook,
					gpointer data)
{
  g_return_if_fail (dis != NULL);
  dis->global_post.func = hook;
  dis->global_post.data = data;
}

/* dispatch a command */
gboolean /* TRUE if success, FALSE otherwise */
mix_vm_cmd_dispatcher_dispatch (mix_vm_cmd_dispatcher_t *dis,
				mix_vm_command_t cmd, const gchar *arg)
{
  g_return_val_if_fail (dis != NULL, FALSE);
  
  if (dis->global_pre.func)
    (dis->global_pre.func)(dis, cmd, arg, dis->global_pre.data);
  
  if (cmd < MIX_CMD_INVALID)
    {
      if (dis->pre_hooks[cmd].func)
	(dis->pre_hooks[cmd].func)(dis, arg, dis->pre_hooks[cmd].data);
      dis->result = (commands_[cmd].func)(dis, arg);
      if (dis->post_hooks[cmd].func)
	(dis->post_hooks[cmd].func)(dis, arg, dis->post_hooks[cmd].data);
    }
  else
    {
      fprintf (dis->err, "Unknown command. Try: help\n");
    }

  if (dis->global_post.func)
    (dis->global_post.func)(dis, cmd, arg, dis->global_post.data);

  fflush (dis->out);
  fflush (dis->err);
  return dis->result;
}

/* dispatch a command in text format */
gboolean
mix_vm_cmd_dispatcher_dispatch_text (mix_vm_cmd_dispatcher_t *dis,
				     const gchar *text)
{
  gchar *cp, *arg = "";
  int k = 0;
  gboolean result;
  
  g_return_val_if_fail (dis != NULL, FALSE);
  g_return_val_if_fail (text != NULL, FALSE);
  
  cp = g_strdup (text);
  while (cp[k] && !isspace (cp[k])) ++k;
  if (cp[k])
    {
      cp[k] = '\0'; ++k;
      while (cp[k] && isspace (cp[k])) ++k;
      arg = cp + k;
    }

  result = mix_vm_cmd_dispatcher_dispatch (dis,
					   mix_vm_command_from_string (cp),
					   arg);
  g_free (cp);
  return result;
}

/* get the last dispatch's result */
gboolean
mix_vm_cmd_dispatcher_get_last_result (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, FALSE);
  return dis->result;
}

/* get total uptime */
mix_time_t
mix_vm_cmd_dispatcher_get_uptime (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, 0);
  return dis->uptime;
}

/* get program total time */
mix_time_t
mix_vm_cmd_dispatcher_get_progtime (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, 0);
  return dis->progtime;
}

/* get time lapse */
mix_time_t
mix_vm_cmd_dispatcher_get_laptime (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, 0);
  return dis->laptime;
}

/* toggle time printing */
void
mix_vm_cmd_dispatcher_print_time (mix_vm_cmd_dispatcher_t * dis, gboolean print)
{
  g_return_if_fail (dis != NULL);
  dis->printtime = print;
}

/* get the mix vm */
const mix_vm_t *
mix_vm_cmd_dispatcher_get_vm (const mix_vm_cmd_dispatcher_t *dis)
{
  g_return_val_if_fail (dis != NULL, NULL);
  return dis->vm;
}


/* trace current instruction */
static void
trace_ (mix_vm_cmd_dispatcher_t *dis)
{
  enum {BUFFER_LEN = 128};
  static gchar STRINS[BUFFER_LEN];
  
  const mix_src_file_t *file = mix_vm_get_src_file (dis->vm);
  const gchar *line = "\n";
  mix_address_t loc = mix_vm_get_prog_count (dis->vm);
  mix_word_t ins = mix_vm_get_addr_contents (dis->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 (dis->vm);
      if (b > 0) line = mix_src_file_get_line (file, b);
    }
  
  fprintf (dis->out, "%d: [%-15s]\t%s", (gint)loc, STRINS, line);
}

/* run a program tracing executed instructions */
static int
run_and_trace_ (mix_vm_cmd_dispatcher_t *dis)
{
  int k = MIX_VM_OK;
  if (!dis->trace)
    return mix_vm_run (dis->vm);
  else while (k == MIX_VM_OK)
    {
      trace_ (dis);
      k = mix_vm_exec_next (dis->vm);
    }
  return k;
}

/* print time statistics */
static void
print_time_ (mix_vm_cmd_dispatcher_t *dis)
{
  dis->laptime = mix_vm_get_uptime(dis->vm) - dis->uptime;
  dis->uptime += dis->laptime;
  dis->progtime += dis->laptime;
  if (dis->printtime)
    fprintf( dis->out,
	     _("Elapsed time: %ld /Total program time: %ld (Total uptime: %ld)\n"),
	     dis->laptime, dis->progtime, dis->uptime);
}


/* commands */

static gboolean
cmd_help_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  static const int NO_OF_COLS = 6;
  
  int i;
  int printed = 0;
     
  for (i = 0; commands_[i].name; i++)
    {
      if (!arg || !*arg)
	{
	  fprintf (dis->out ,_("%s\t\t%s\n"), commands_[i].name, 
		   _(commands_[i].doc));
	  printed++;
	}
      else if ((strcmp (arg, commands_[i].name) == 0))
	{
	  fprintf (dis->out ,_("%s\t\t%s.\n\t\tUsage: %s\n"),
		   commands_[i].name,  _(commands_[i].doc),
		   commands_[i].usage);
	  printed++;
	}
    }
  
  if (!printed)
    {
      fprintf (dis->out,
	       _("No commands match `%s'.  Possibilities are:\n"), arg);
     
      for (i = 0; commands_[i].name; i++)
	{
	  if (printed == NO_OF_COLS)
	    {
	      printed = 0;
	      fprintf (dis->out, "\n");
	    }
     
	  fprintf (dis->out, "%s\t", commands_[i].name);
	  printed++;
	}
     
      if (printed)
	fprintf (dis->out, "\n");
    }
  return TRUE;
}

static gboolean
cmd_load_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  errno = 0;
  if (arg == NULL || *arg == '\0')
    {
      fputs (_("Missing file name\n"), dis->err);
      return FALSE;
    }
  mix_eval_remove_symbols_from_table (dis->eval,
				      mix_vm_get_symbol_table (dis->vm));
  if (!mix_vm_load_file (dis->vm, arg) )
    {
      fprintf (dis->err, _("Cannot load %s: "), arg);
      if ( errno == 0 )
	fputs (_("Wrong file format\n"), dis->err);
      else
	fprintf (dis->err, "%s\n", strerror (errno));
      return FALSE;
    }

  if (dis->program != arg)
    {
        if (dis->program) g_free (dis->program);
	dis->program = g_strdup (arg);
    }

  mix_eval_set_symbols_from_table (dis->eval,
				   mix_vm_get_symbol_table (dis->vm));

  fprintf (dis->out, _("Program loaded. Start address: %d\n"),
	   mix_vm_get_prog_count (dis->vm));
  
  dis->laptime = dis->progtime = 0;
  return TRUE;
}

static gboolean
cmd_edit_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (dis->editor == NULL)
    {
      fputs (_("Editor not specified (set MDK_EDITOR)\n"), dis->err);
      return FALSE;
    }
  if (!arg || *arg == '\0') arg = mix_vm_cmd_dispatcher_get_src_file_path (dis);
  if (!arg)
    {
      fputs (_("MIXAL source file path not found\n"), dis->err);
      return FALSE;
    }
  else
    {
      gchar *cmd = g_strdup_printf (dis->editor, arg);
      fputs (cmd, dis->out);
      system (cmd);
      fputs (_(" ...done\n"), dis->out);
      g_free (cmd);
      return TRUE;
    }
}

static gboolean
cmd_compile_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (dis->assembler == NULL)
    {
      fputs (_("MIX assembler not specified\n"), dis->err);
      return FALSE;
    }
  if (!arg || *arg == '\0') arg = mix_vm_cmd_dispatcher_get_src_file_path (dis);
  if (!arg)
    {
      fputs (_("MIXAL source file path not found\n"), dis->err);
      return FALSE;
    }
  else
    {
      gchar *cmd = g_strdup_printf (dis->assembler, arg);
      fputs (cmd, dis->out);
      fputs ("\n", dis->out);
      if (system (cmd) == EXIT_SUCCESS)
	fputs (_("Successful compilation\n"), dis->out);
      g_free (cmd);
      return TRUE;
    }
}

static gboolean
cmd_run_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (arg != NULL && *arg != '\0' && cmd_load_ (dis, arg) != TRUE) 
      return FALSE;
  
  if (mix_vm_is_halted (dis->vm)) cmd_load_ (dis, dis->program);
    
  fputs (_("Running ...\n"), dis->out);

  switch (run_and_trace_ (dis))
    {
    case MIX_VM_HALT:
      fputs (_("... done\n"), dis->out);
      break;
    case MIX_VM_BREAK:
      {
	gulong line = mix_vm_get_break_lineno (dis->vm);
	if (line != 0)
	  fprintf (dis->out,
		   _("... stopped: breakpoint at line %ld (address %d)\n"), 
		   line, mix_vm_get_prog_count (dis->vm));
	else
	  fprintf (dis->out, _("... stopped: breakpoint at address %d\n"),
		   mix_vm_get_prog_count (dis->vm));
      }
      break;
    case MIX_VM_COND_BREAK:
      {
	gulong line = mix_vm_get_break_lineno (dis->vm);
	if (line != 0)
	  fprintf (dis->out, _("... stopped: %s (line %ld, address %d)\n"),
		   mix_vm_get_last_breakpoint_message (dis->vm),
		   line, mix_vm_get_prog_count (dis->vm));
	else
	  fprintf (dis->out, _("... stopped: %s (address %d)\n"),
		   mix_vm_get_last_breakpoint_message (dis->vm),
		   mix_vm_get_prog_count (dis->vm));
      }
      break;
    case MIX_VM_ERROR:
      fputs (_("... error executing loaded file"), dis->err);
      break;
    default:
      g_assert_not_reached ();
      break;
    }
  print_time_ (dis);

  return TRUE;
}

static gboolean
cmd_next_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  int ins_no = 1;
  int k;

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

  if (mix_vm_is_halted (dis->vm)) cmd_load_ (dis, dis->program);
    
  while ( ins_no-- > 0 )
    {
      if (dis->trace) trace_ (dis);
      k = mix_vm_exec_next (dis->vm);
      if (k == MIX_VM_HALT)
	{
	  fprintf (dis->err, _("End of program reached at address %d\n"),
		   mix_vm_get_prog_count (dis->vm));
	  break;
	}
      else if (k == MIX_VM_ERROR)
	{
	  fprintf (dis->err, _("Error at address %d\n"),
		   mix_vm_get_prog_count (dis->vm));
	  break;
	}
    }
  print_time_ (dis);

  return TRUE;
}

static gboolean
cmd_pc_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  fprintf (dis->out, "Current address: %d\n", mix_vm_get_prog_count (dis->vm));
  return TRUE;
}

static gboolean
cmd_psym_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  gboolean result = FALSE;
  const mix_symbol_table_t *table = mix_eval_symbol_table (dis->eval);
  if ( table == NULL )
    fputs (_("Symbol table not available\n"), dis->err);
  else if (arg != NULL && *arg != '\0')
    {
      if ( mix_symbol_table_is_defined (table, arg) )
	{
	  mix_word_print_to_file (mix_symbol_table_value (table, arg),
				  NULL, dis->out);
	  putc ('\n', dis->out);
	  result = TRUE;
	}
      else
	fprintf (dis->out, _("%s: symbol not defined\n"), arg);
    }
  else
    {
      mix_symbol_table_print (table, MIX_SYM_ROWS, dis->out, TRUE);
      result = TRUE;
    }
      
  return result;
}

static gboolean
cmd_preg_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  mix_dump_context_set_opt (dis->dump, MIX_DUMP_NONE);
  if ( strlen (arg) == 0 )
    mix_dump_context_add_opt (dis->dump, MIX_DUMP_rALL);
  else switch (*arg)
    {
    case 'A':
      mix_dump_context_add_opt (dis->dump, MIX_DUMP_rA);
      break;
    case 'X':
      mix_dump_context_add_opt (dis->dump, MIX_DUMP_rX);
      break;
    case 'J':
      mix_dump_context_add_opt (dis->dump, MIX_DUMP_rJ);
      break;
    case 'I':
      {
	if ( strlen (arg) == 1 )
	  mix_dump_context_add_opt (dis->dump, 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 (dis->err, _("Invalid I index: %d\n"), i);
		return FALSE;
	      }
	    mix_dump_context_add_opt (dis->dump, opt[i]);
	  }
      }
      break;
    default:
      fprintf (dis->err, _("Invalid argument: %s\n"), arg);
      return FALSE;
    }
  mix_vm_dump (dis->vm, dis->dump);
  return TRUE;
}

static gboolean
cmd_pflags_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  mix_dump_context_set_opt (dis->dump, MIX_DUMP_CMP | MIX_DUMP_OVER);
  mix_vm_dump (dis->vm, dis->dump);
  return TRUE;
}

static gboolean
cmd_pall_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  mix_dump_context_set_opt (dis->dump, MIX_DUMP_ALL_NOMEM);
  mix_vm_dump (dis->vm, dis->dump);
  return TRUE;
}

static gboolean
cmd_pmem_ (mix_vm_cmd_dispatcher_t *dis, const gchar *carg)
{
  glong begin = MIX_SHORT_ZERO, end = MIX_SHORT_ZERO;
  int i = 0;
  gboolean error = FALSE;
  gchar *arg = NULL;
  
  if ( strlen (carg) == 0 )
    {
      fputs (_("Missing memory address\n"), dis->err);
      return FALSE;
    }
  arg = g_strdup (carg);
  while (isdigit (arg[i]))
    i++;
  while (isspace (arg[i]))
    i++;
  if (arg[i] == '\0')
      begin = end = atol (arg);
  else if (arg[i] == '-')
    {
      gchar *narg;
      arg[i++] = '\0';
      begin = atol (arg);
      narg = arg + i;
      i = 0;
      while (isdigit (narg[i]))
	i++;
      while (isspace (narg[i]))
	i++;
      if (narg[i] != '\0') 
	error = TRUE;
      else 
	end = atol (narg);
    }
  else
    error = TRUE;
  
  if (error)
    {
      fprintf (dis->err, _("Invalid argument: %s\n"), arg);
      cmd_help_ (dis, "pmem");
    }
  else if ( end < begin || end > MIX_VM_CELL_NO - 1 )
    {
      fprintf (dis->err, _("Invalid range: %ld-%ld\n"), begin, end);
      error = TRUE;
    }
  else
    {
      mix_dump_context_set_opt (dis->dump, MIX_DUMP_CELLS);
      mix_dump_context_range (dis->dump, mix_short_new (begin),
			      mix_short_new (end + 1));
      mix_vm_dump (dis->vm, dis->dump);
    }
  g_free (arg);
  return !error;
}

static gboolean
cmd_sreg_ (mix_vm_cmd_dispatcher_t *dis, const gchar *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 (dis->vm, mix_word_new (value));
		break;
	      case 'X':
		mix_vm_set_rX (dis->vm, mix_word_new (value));
		break;
	      case 'J':
		if ( value >= 0 )
		  mix_vm_set_rJ (dis->vm, mix_short_new (value));
		else
		  ok = FALSE;
		break;
	      case 'I':
		{
		  guint k = arg[1] - '0';
		  if ( k < 7 ) 
		    mix_vm_set_rI (dis->vm, k, mix_short_new (value));
		  else
		    ok = FALSE;
		}
		break;
	      default:
		ok = FALSE;
	      }
	}
    }
  if (!ok)
    {
      fprintf (dis->err, _("Invalid argument: %s\n"), arg);
      cmd_help_ (dis, "sreg");
    }
    
  return ok;
}

static gboolean
cmd_scmp_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  gboolean ok = (strlen (arg) == 1);
  if (ok) switch (arg[0])
    {
    case 'L':
      mix_vm_set_cmpflag (dis->vm, mix_LESS);
      break;
    case 'E':
      mix_vm_set_cmpflag (dis->vm, mix_EQ);
      break;
    case 'G':
      mix_vm_set_cmpflag (dis->vm, mix_GREAT);
      break;
    default:
      ok = FALSE;
    }
  if (!ok)
    {
      fprintf (dis->err, _("Invalid argument: %s\n"), arg);
      cmd_help_ (dis, "scmp");
    }
  
  return ok;
}

static gboolean
cmd_sover_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  gboolean ok = (strlen (arg) == 1);
  if (ok) switch (arg[0])
    {
    case 'T':
      mix_vm_set_overflow (dis->vm, TRUE);
      break;
    case 'F':
      mix_vm_set_overflow (dis->vm, FALSE);
      break;
    default:
      ok = FALSE;
    }
  if (!ok)
    {
      fprintf (dis->err, _("Invalid argument: %s\n"), arg);
      cmd_help_ (dis, "sover");
    }
  
  return ok;
}

static gboolean
cmd_smem_ (mix_vm_cmd_dispatcher_t *dis, const gchar *carg)
{
  gboolean ok = (strlen (carg) > 2 && isdigit (carg[0]));
  glong addr = -1;
  glong value = 0;
  int k = 0;
  gchar *arg = NULL;
  
  if (ok)
    {
      arg = g_strdup (carg);
      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 (dis->vm, mix_short_new (addr),
			      mix_word_new (value));
  else
    {
      fprintf (dis->err, _("Invalid argument: %s\n"), arg);
      cmd_help_ (dis, "smem");
    }
  if (arg) g_free (arg);
  
  return ok;
}

static gboolean
cmd_ssym_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  gboolean result = FALSE;
  if (arg == NULL || strlen(arg) == 0)
    {
      fprintf (dis->err, _("Missing arguments\n"));
      cmd_help_ (dis, "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_ (dis, w);
	  if (mix_eval_last_error (dis->eval) == MIX_EVAL_OK) {
	    mix_eval_set_symbol (dis->eval, s, mix_eval_value (dis->eval));
	    result = TRUE;
	  }
	}
      else
	{
	  fprintf (dis->err, _("Wrong argument number\n"));
	  cmd_help_ (dis, "ssym");
	}
      g_free (a);
    }
  return result;
}

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

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

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

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


static gboolean
cmd_cabp_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (strlen (arg) != 0)
    {
      fprintf (dis->err, _("Invalid argument: %s\n"), arg);
      cmd_help_ (dis, "cabp");
      return FALSE;
    }
  mix_vm_clear_all_breakpoints (dis->vm);
  return TRUE;
}

static gboolean
cmd_weval_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if ( strlen (arg) == 0 )
    {
      cmd_help_ (dis, "weval");
      return FALSE;
    }
  
  if (mix_eval_expression_with_loc (dis->eval, arg,
				    mix_vm_get_prog_count (dis->vm)) ==
      MIX_EVAL_OK)
    {
      mix_word_print_to_file (mix_eval_value (dis->eval), NULL, dis->out);
      putc ('\n', dis->out);
      return TRUE;
    }
  else
    {
      gint pos = mix_eval_last_error_pos (dis->eval);
      gint k, len = strlen (arg);
      g_assert(pos > -1 && pos <= len);
      for (k = 0; k<pos; ++k) fputc (arg[k], dis->err);
      fputc ('\n', dis->err);
      for (k = 0; k<pos; ++k) fputc (' ', dis->err);
      for (k = pos; k < len; ++k) fputc (arg[k], dis->err);
      fprintf (dis->err, _("\nEvaluation error: %s\n"),
	       mix_eval_last_error_string (dis->eval));
      return FALSE;
    }
}

static gboolean
cmd_w2d_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if ( strlen (arg) == 0 )
    {
      cmd_help_ (dis, "w2d");
      return FALSE;
    }
  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 (dis->err, _("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 (dis->err,
		       _("The expression %s does not fit in a word\n"), arg);
	      success = FALSE;
	    }
	  else
	    {
	      mix_word_t w = mix_bytes_to_word (bytes, k);
	      fprintf (dis->out, "%s%ld\n", is_n? "-":"+",
		       mix_word_magnitude (w));
	    }
	}
      g_free (cp);
  
      return success;
    }
}

static gboolean
cmd_tracing_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  static const gchar *ON = "on";
  static const gchar *OFF = "off";
  if (arg && !strcmp (arg, ON))
    {
      dis->trace = TRUE;
      if (dis->config) mix_config_update (dis->config, TRACING_KEY_, ON);
    }
  else if (arg && !strcmp (arg, OFF))
    {
      dis->trace = FALSE;
      if (dis->config) mix_config_update (dis->config, TRACING_KEY_, OFF);
    }
  else if (arg && !strlen (arg))
    {
      fprintf (dis->out, "Instruction tracing is currently set %s\n",
	       dis->trace ? "ON" : "OFF");
    }
  else 
    cmd_help_ (dis, "tracing");
  return TRUE;
}

static gboolean
cmd_timing_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  static const gchar *ON = "on";
  static const gchar *OFF = "off";
  if (arg && !strcmp (arg, ON))
    {
      dis->printtime = TRUE;
      if (dis->config) mix_config_update (dis->config, TIMING_KEY_, ON);
    }
  else if (arg && !strcmp (arg, OFF))
    {
      dis->printtime = FALSE;
      if (dis->config) mix_config_update (dis->config, TIMING_KEY_, OFF);
    }
  else if (arg && !strlen (arg))
    {
      print_time_ (dis);
    }
  else 
    cmd_help_ (dis, "timing");
  return TRUE;
}
  

static gboolean
cmd_pedit_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  const gchar *ed = mix_vm_cmd_dispatcher_get_editor (dis);
  if (dis)
    fprintf (dis->out, "Edit command: %s\n", ed);
  else
    fprintf (dis->out, "Edit command not set (use sedit)\n");
  return TRUE;
}

static gboolean
cmd_sedit_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (!arg || !strlen (arg)) return cmd_help_ (dis, "sedit");
  mix_vm_cmd_dispatcher_set_editor (dis, arg);
  return TRUE;
}

static gboolean
cmd_pasm_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  const gchar *ed = mix_vm_cmd_dispatcher_get_assembler (dis);
  if (dis)
    fprintf (dis->out, "Compile command: %s\n", ed);
  else
    fprintf (dis->out, "Compile command not set (use sasm)\n");
  return TRUE;
}

static gboolean
cmd_sasm_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (!arg || !strlen (arg)) return cmd_help_ (dis, "sasm");
  mix_vm_cmd_dispatcher_set_assembler (dis, arg);
  return TRUE;
}

static gboolean
cmd_devdir_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (!arg || !strlen (arg))
    fprintf (dis->out, "Device directory: %s\n", mix_device_get_dir ());
  else if (mix_device_set_dir (arg) && dis->config)
    mix_config_set_devices_dir (dis->config, arg);
  return TRUE;
}


static const gint INVALID_REG_ = -2;

static mix_predicate_type_t
get_reg_pred_ (const gchar *arg)
{
  mix_predicate_type_t pred = INVALID_REG_;
  
  switch (*arg)
    {
    case 'A':
      pred = MIX_PRED_REG_A;
      break;
    case 'X':
      pred = MIX_PRED_REG_X;
      break;
    case 'J':
      pred = MIX_PRED_REG_J;
      break;
    case 'I':
      {
	if ( strlen (arg) == 2 )
	  {
	    int i = arg[1] - '1';
	    if (i >= 0 && i < 6)
	      pred = MIX_PRED_REG_I1 - 1 + i;
	  }
      }
      break;
    default:
      break;
    }
  return pred;
}

static gboolean
cmd_sbpr_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  mix_predicate_type_t pred = get_reg_pred_ (arg);
  if (pred != INVALID_REG_)
    {
      mix_vm_set_conditional_breakpoint (dis->vm, dis->preds[pred]);
      fprintf (dis->out, _("Conditional breakpoint on r%s change set\n"), arg);
      return TRUE;
    }
  else
    {
      fprintf (dis->err, "Invalid argument %s\n", arg);
      return FALSE;
    }
}

static gboolean
cmd_cbpr_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  mix_predicate_type_t pred = get_reg_pred_ (arg);
  if (pred != INVALID_REG_)
    {
      if (mix_vm_clear_conditional_breakpoint (dis->vm, dis->preds[pred]))
	fprintf (dis->out,
		 _("Conditional breakpoint on r%s change removed\n"),
		 arg);
      else
	fprintf (dis->err, _("No breakpoint set on r%s change\n"), arg);
      return TRUE;
    }
  else
    {
      fprintf (dis->err, _("Invalid argument %s\n"), arg);
      return FALSE;
    }
}

static gboolean
cmd_sbpm_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  gint add = atoi (arg);
  gpointer key, value;
  if (add < 0 || add > MIX_VM_CELL_NO)
    {
      fprintf (dis->out, _("Invalid memory address: %s\n"), arg);
      return FALSE;
    }
  if (!g_hash_table_lookup_extended (dis->mem_preds, GINT_TO_POINTER (add),
				     &key, &value))
    {
      mix_predicate_t *new_pred = mix_predicate_new (MIX_PRED_MEM);
      mix_predicate_set_mem_address (new_pred, add);
      g_hash_table_insert (dis->mem_preds,
			   GINT_TO_POINTER (add), (gpointer)new_pred);
      mix_vm_set_conditional_breakpoint (dis->vm, new_pred);
    }
  fprintf (dis->out, _("Conditional breakpoint on mem cell no. %d set\n"), add);
  return TRUE;
}

static gboolean
cmd_cbpm_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  gint add = atoi (arg);
  gpointer key, value;
  if (add < 0 || add > MIX_VM_CELL_NO)
    {
      fprintf (dis->out, _("Invalid memory address: %s\n"), arg);
      return FALSE;
    }
  if (g_hash_table_lookup_extended (dis->mem_preds, GINT_TO_POINTER (add),
				    &key, &value))
    {
      g_hash_table_remove (dis->mem_preds, key);
      mix_vm_clear_conditional_breakpoint (dis->vm, (mix_predicate_t *)value);
      mix_predicate_delete ((mix_predicate_t *)value);
      fprintf (dis->out,
	       _("Conditional breakpoint on mem cell no. %d removed\n"), add);
    }
  else
    {
      fprintf (dis->out, _("No conditional breakpoint set at address %d\n"),
	       add);
    }
  return TRUE;
}

static gboolean
cmd_sbpo_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (arg && strlen (arg))
    fprintf (dis->err, _("Unexpected argument: %s\n"), arg);
  else
    {
      mix_vm_set_conditional_breakpoint (dis->vm, dis->preds[MIX_PRED_OVER]);
      fprintf (dis->out,
	       _("Conditional breakpoint on overflow toggled set\n"));
    }
  return TRUE;
}

static gboolean
cmd_cbpo_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (arg && strlen (arg))
    fprintf (dis->err, _("Unexpected argument: %s\n"), arg);
  else
    {
      if (mix_vm_clear_conditional_breakpoint
	  (dis->vm, dis->preds[MIX_PRED_OVER]))
	fprintf (dis->out,
		 _("Conditional breakpoint on overflow toggled removed.\n"));
      else
	fprintf (dis->err, _("No breakpoint set on overflow toggle\n"));
    }
  return TRUE;
}

static gboolean
cmd_sbpc_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (arg && strlen (arg))
    fprintf (dis->err, _("Unexpected argument: %s\n"), arg);
  else
    {
      mix_vm_set_conditional_breakpoint (dis->vm, dis->preds[MIX_PRED_CMP]);
      fprintf (dis->out,
	       _("Conditional breakpoint on comparison flag changed set.\n"));
    }
  return TRUE;
}

static gboolean
cmd_cbpc_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  if (arg && strlen (arg))
    fprintf (dis->err, _("Unexpected argument: %s\n"), arg);
  else
    {
      if (mix_vm_clear_conditional_breakpoint
	  (dis->vm, dis->preds[MIX_PRED_CMP]))
	fprintf
	  (dis->out,
	   _("Conditional breakpoint on comparison flag changed removed.\n"));
      else
	fprintf (dis->err, _("No breakpoint set on comparison flag change\n"));
    }
  return TRUE;
}

static gboolean
cmd_pbt_ (mix_vm_cmd_dispatcher_t *dis, const gchar *arg)
{
  enum {SIZE = 256};
  static gchar BUFFER[SIZE];
  gint no = atoi (arg);
  gint k = 0, address;
  guint line;
  const mix_src_file_t *file = mix_vm_get_src_file (dis->vm);
  char *name = file ? g_basename (mix_src_file_get_path (file)) : NULL;

  const GSList *add = mix_vm_get_backtrace (dis->vm);
  while (add && (no == 0 || k < no))
    {
      BUFFER[0] = '\0';
      address = GPOINTER_TO_INT (add->data);
      line = mix_vm_get_address_lineno (dis->vm, address);
      if (line && file)
	{
	  int j = 0;
	  snprintf (BUFFER, SIZE, "%s", mix_src_file_get_line (file, line));
	  while (!isspace (BUFFER[j])) j++;
	  BUFFER[j] = '\0';
	}
      if (strlen (BUFFER) == 0)	snprintf (BUFFER, SIZE, "%d", address);
      fprintf (dis->out, "#%d\t%s\tin %s%s:%d\n", k, BUFFER, name,
	       MIX_SRC_DEFEXT, line);
      ++k;
      add = add->next;
    }
  /*  if (name) g_free (name); */
  return TRUE;
}