/*

Freely Distributable 68HC11 Simulator Package

Copyright (c) 1997-1998 The University of Texas
All Rights Reserved.

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.
  
The GNU Public License is available in the file LICENSE, or you
can write to the Free Software Foundation, Inc., 59 Temple Place -
Suite 330, Boston, MA 02111-1307, USA, or you can find it on the
World Wide Web at http://www.fsf.org.

Authors: Brian Evans and Saleem Marwat
Version: @(#)commands.c	1.4	01/19/98

Department of Electrical and Computer Engineering
The University of Texas, Austin, TX 78712-1084

To add simulator commands for a debugger for a particular
board, add an entry to simulatorCommandTables.
*/

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

#include "main.h"
#include "commands.h"

static const char* VersionInfo =
"The University of Texas at Austin MC68HC11 Simulator Version %s.\n"
"Copyright (c) 1997-1998 The University of Texas.  All Rights Reserved.\n"
"Released as freely distributable software under the GNU license.\n"
"Authored by Brian Evans and Saleem Marwat.\n"
"Based in part on Version 1.3 of Sim68 by Tomaso Paoletti.\n";

static const char* CopyrightInformation =
"Freely Distributable 68HC11 Simulator Package\n"
"\n"
"Copyright (c) 1997-1998 The University of Texas\n"
"All Rights Reserved.\n"
"\n"
"This program is free software; you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation; either version 2 of the License, or\n"
"(at your option) any later version.\n"
"\n"
"This program is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
"GNU General Public License for more details.\n"
"\n"
"The GNU Public License is available in the file LICENSE, or you\n"
"can write to the Free Software Foundation, Inc., 59 Temple Place -\n"
"Suite 330, Boston, MA 02111-1307, USA, or you can find it on the\n"
"World Wide Web at http://www.fsf.org.\n";

/* Every message must have a %s format specification in it. */
static const char* ErrorMessages[] = {
  "No error.%s",
  "One or more missing arguments in '%s'.",
  "Extra characters '%s' after the command were ignored.",
  "Cannot add a breakpoint at address '%s'.",
  "No more breakpoints may be added: '%s' not added.",
  "Invalid or missing address given in '%s'.",
  "No breakpoint at '%s'.",
  "Cannot find a breakpoint at address '%s'.",
  "Invalid or missing filename given in '%s'.",
  "Invalid or missing location '%s'",
  "Invalid or missing value given for '%s'.",
  "Invalid or missing breakpoint '%s'.",
  "Not implemented yet: '%s' ignored.",
  "No previous load command has been given.%s'",
  "The number of steps %s must be positive.",
  "The run ended because the PC reached an invalid value.%s",
  "Unrecognized command '%s'.",
  "Unrecognized board: cannot evaluate '%s'."
};

#define MC_SIM_EXIT_PROGRAM         -1
#define MC_SIM_NO_ERROR              0
#define MC_SIM_MISSING_ARGUMENTS     1
#define MC_SIM_EXTRA_CHARS           2
#define MC_SIM_ZERO_BREAKPOINT       3
#define MC_SIM_NO_MORE_BREAKPOINTS   4
#define MC_SIM_INVALID_ADDRESS       5
#define MC_SIM_NO_BREAKPOINT         6
#define MC_SIM_UNKNOWN_BREAKPOINT    7
#define MC_SIM_INVALID_FILENAME      8
#define MC_SIM_INVALID_LOCATION      9
#define MC_SIM_INVALID_VALUE        10
#define MC_SIM_INVALID_BREAKPOINT   11
#define MC_SIM_NOT_IMPLEMENTED      12
#define MC_SIM_NO_LOAD_COMMAND      13
#define MC_SIM_NONPOSITIVE_STEPS    14
#define MC_SIM_INVALID_PC           15
#define MC_SIM_UNRECOGNIZED_COMMAND 16
#define MC_SIM_UNRECOGNIZED_BOARD   17
 
#define MC_SIM_MAX_ERROR_NUMBER     17

/* Check for no arguments */
static int assertNoMoreArguments(const char* input) {
    return ( input && *input ) ? MC_SIM_EXTRA_CHARS : MC_SIM_NO_ERROR;
}
     
static int assertMoreArguments(const char* input) {
    return ( input && *input ) ? MC_SIM_NO_ERROR : MC_SIM_MISSING_ARGUMENTS;
}

/*
Return the error generated by a simulator command.
*/
const char* MCSimCommandErrorMessage(int errorNumber) {
    const char* errorMessage = "";
    if ( errorNumber > 0 && errorNumber <= MC_SIM_MAX_ERROR_NUMBER ) {
      return ErrorMessages[errorNumber];
    }
    return errorMessage;
}


int MCSimBreakAddCommand(const char* input, MCSimState *mcsimptr) {
  int retval = assertMoreArguments(input);
  if (retval == MC_SIM_NO_ERROR) {
    if (mcsimptr->load) {
      long add = get_pc(mcsimptr->state);
      sscanf(input, "%lx", &add);
      if ((add >= 0) && (add < M68HC11_MEMSIZE)) {
        mcsimptr->breaks[add] = -1;
      }
      else {
	retval = MC_SIM_INVALID_ADDRESS;
      }
    }
    else {
      retval = MC_SIM_NO_LOAD_COMMAND;
    }
  }
  return retval;
}

int MCSimBreakDelCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertMoreArguments(input);
  if (retval == MC_SIM_NO_ERROR) {
    if (mcsimptr->load) {
      long add = get_pc(mcsimptr->state);
      sscanf(input, "%lx", &add);
      if ((add >= 0) && (add < M68HC11_MEMSIZE)) {
        mcsimptr->breaks[add] = 0;
      }
      else {
	retval = MC_SIM_INVALID_ADDRESS;
      }
    }
    else {
      retval = MC_SIM_NO_LOAD_COMMAND;
    }
  }
  return retval;
}

int MCSimCopyrightCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  printf(CopyrightInformation);
  fflush(stdout);
  return retval;
}

int MCSimCycleCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  if (retval == MC_SIM_NO_ERROR) {
    printf("cycles=%ld\n", mcsimptr->state->t);
    fflush(stdout);
  }
  return retval;
}

int MCSimDasmOffCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  if (retval == MC_SIM_NO_ERROR) {
    debug = FALSE;
  }
  return retval;
}

int MCSimDasmOnCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  if (retval == MC_SIM_NO_ERROR) {
    debug = TRUE;
  }
  return retval;
}

int MCSimDisassembleCommand(const char* input, MCSimState* mcsimptr) {
  int retval = MC_SIM_NO_ERROR;
  if (mcsimptr->load) {
    long add = get_pc(mcsimptr->state);
    int n = 20;
    sscanf(input, "%lx %d", &add, &n);
    if ((add >= 0) && (add < M68HC11_MEMSIZE)) {
      while (n-- && (n > 0)) {
        printf("%c %04lx\t", mcsimptr->breaks[add] ? '*' : ' ', add);
	add += disassemble(stdout, mcsimptr->memory, add);
      }
    }
    else {
      retval = MC_SIM_INVALID_ADDRESS;
    }
  }
  else {
    retval = MC_SIM_NO_LOAD_COMMAND;
  }
  return retval;
}

int MCSimDisplayMemoryCommand(const char* input, MCSimState* mcsimptr) {
  int retval = MC_SIM_NO_ERROR;
  long add = get_pc(mcsimptr->state);
  int n = -1;
  sscanf(input, "%lx %d", &add, &n);
  mem_dump(mcsimptr->state, mcsimptr->memory, add, n);
  return retval;
}

int MCSimDummyCommand(const char* input, MCSimState* mcsimptr) {
  return MC_SIM_NO_ERROR;
}

int MCSimExitCommand(const char* input, MCSimState* mcsimptr) {
  return MC_SIM_EXIT_PROGRAM;
}

int MCSimInterruptCommand(const char* input, MCSimState* mcsimptr) {
#ifdef M68HC11_HANDLE_INTERRUPTS
  static last_irq = 13;		/* default is serial transfer done */
  long t = 0;
  int retval = assertMoreArguments(input);
  if (retval == MC_SIM_NO_ERROR) {
    /* Interrupts still in testing phase */
    if (mcsimptr->load) {
      long n = last_irq;		/* default is last one used */
      t = state->t;		                /* default is *now* */
      sscanf(input, "%lx %ld", &n, &t);
      /* schedule_interrupt(n, t); */
    }
    else {
      retval = MC_SIM_NO_LOAD_COMMAND;
    }
  }
#else
  int retval = MC_SIM_NOT_IMPLEMENTED;
#endif
  return retval;
}

int MCSimLoadCommand(const char* input, MCSimState *mcsimptr) {
  int retval = assertMoreArguments(input);
  if ( retval == MC_SIM_NO_ERROR ) {
    FILE* f = fopen(input, "r");
    if (f != NULL) {
      reset(mcsimptr->state, mcsimptr->memory, mcsimptr->breaks);
      load_code(f, mcsimptr->memory);
      mcsimptr->load = TRUE;
      strcpy(mcsimptr->filename, input);
      fclose(f);
    }
    else {
      retval = MC_SIM_INVALID_FILENAME;
    }
  }
  return retval;
}

int MCSimMC68StateCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  MC6811state(mcsimptr->state, mcsimptr->memory,
              mcsimptr->breaks, mcsimptr->filename);
  return retval;
}

int MCSimNotImplementedCommand(const char* input, MCSimState* mcsimptr) {
  return MC_SIM_NOT_IMPLEMENTED;
}

int MCSimResetCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  if ( retval == MC_SIM_NO_ERROR ) {
    reset(mcsimptr->state, mcsimptr->memory, mcsimptr->breaks);	
    if (mcsimptr->load) {
      FILE* f = fopen(mcsimptr->filename, "r");
      if (f) {
        load_code(f, mcsimptr->memory);
        fclose(f);
      }
      else {
        mcsimptr->load = FALSE;
        retval = MC_SIM_INVALID_FILENAME;
      }
    }
  }
  return retval;
}

int MCSimRunCommand(const char* input, MCSimState* mcsimptr) {
  int retval = MC_SIM_NO_ERROR;
  if (mcsimptr->load) {
      long add = get_pc(mcsimptr->state);
      int n = 1;
      sscanf(input, "%lx %d", &add, &n);
      if ((add >= 0) && (add < M68HC11_MEMSIZE)) {
        set_pc(mcsimptr->state, add);
        multi_step(n, mcsimptr->state, mcsimptr->memory, mcsimptr->breaks);
      }
      else {
	retval = MC_SIM_INVALID_ADDRESS;
      }
  }
  else {
      retval = MC_SIM_NO_LOAD_COMMAND;
  }  
  return retval;
}

int MCSimSetMemoryCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertMoreArguments(input);
  if ( retval == MC_SIM_NO_ERROR ) {
    long add = get_pc(mcsimptr->state);
    int n = 1;
    if (sscanf(input, "%lx %d", &add, &n) == 2) {
      if ((add >= 0) && (add < M68HC11_MEMSIZE)) {
        set_mem8(mcsimptr->memory, add, n);
      }
      else {
        retval = MC_SIM_INVALID_ADDRESS;
      }
    }
    else {
      retval = MC_SIM_MISSING_ARGUMENTS;
    }
  }
  return retval;
}

int MCSimSetRegisterCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertMoreArguments(input);
  if ( retval == MC_SIM_NO_ERROR ) {
    char c = 0;
    int n = 1;
    if (sscanf(input, "%c %d", &c, &n) == 2) {
      setReg(mcsimptr->state, c, n);
    }
    else if (sscanf(input, "%c=%d", &c, &n) == 2) {
      setReg(mcsimptr->state, c, n);
    }
    else {
      retval = MC_SIM_MISSING_ARGUMENTS;
    }
  }
  return retval;
}

int MCSimShowBreaksCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  if (retval == MC_SIM_NO_ERROR) {
    showBreaks(mcsimptr->state, mcsimptr->breaks);
  }
  return retval;
}

int MCSimSimStateCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  if ( retval == MC_SIM_NO_ERROR ) {
    MC6811state(mcsimptr->state, mcsimptr->memory,
                mcsimptr->breaks, mcsimptr->filename);
  }
  return retval;
}

int MCSimStepCommand(const char* input, MCSimState *mcsimptr) {
  int retval = MC_SIM_NO_ERROR;
  if (mcsimptr->load) {
    int n = 1;
    sscanf(input, "%d", &n);
    if (debug) {
      debug_multi_step(n, mcsimptr->state, mcsimptr->memory, mcsimptr->breaks);
    }
    else {
      multi_step(n, mcsimptr->state, mcsimptr->memory, mcsimptr->breaks);
    }
  }
  else {
    retval = MC_SIM_NO_LOAD_COMMAND;
  }
  return retval;
}

int MCSimStateCommand(const char* input, MCSimState* mcsimptr) {
  int retval = assertNoMoreArguments(input);
  if ( retval == MC_SIM_NO_ERROR ) {
    registers(mcsimptr->state);
  }
  return retval;
}

int MCSimTempBreakCommand(const char* input, MCSimState* mcsimptr) {
  int retval = MC_SIM_NO_ERROR;
  if (mcsimptr->load) {
    long add = get_pc(mcsimptr->state);
    int n = 1;
    sscanf(input, "%lx %d", &add, &n);
    if ((add >= 0) && (add < M68HC11_MEMSIZE)) {
      mcsimptr->breaks[add] = n;
    }
    else {
      retval = MC_SIM_INVALID_ADDRESS;
    }
  }
  else {
    retval = MC_SIM_NO_LOAD_COMMAND;
  }
  return retval;
}

int MCSimVersionCommand(const char* input, MCSimState* mcsimptr) {
    int retval = assertNoMoreArguments(input);
    printf(VersionInfo, VersionString);
    fflush(stdout);
    return retval;
}

MCSimCommandFunctions MCSimCommandFunctionsList[] = {
    { "+break",   6, MCSimBreakAddCommand,
      "[addr] - sets break point at addr (pc by default)" },
    { "-break",   6, MCSimBreakDelCommand,
      "[addr] - clears break point at address addr (pc by default)" },
    { "copyright",9, MCSimCopyrightCommand,
      "- displays the copyright information for the simulator" },
    { "cycle",    5, MCSimCycleCommand,
      "- prints number of instruction cycles executed since the last reset." },
    { "dasm",     4, MCSimDisassembleCommand,
      "[addr [n]] - disassemble n lines (20 by default) at addr (pc by default)" },
    { "+dasm",    5, MCSimDasmOnCommand,
      "- turns on printing of instructions when they are decoded." },
    { "-dasm",    5, MCSimDasmOffCommand,
      "- turns off printing of instructions when they are decoded." },
    { "displaymem",10, MCSimDisplayMemoryCommand,
      "addr n - displays the first n values of memory at address addr" },
    { "exit",     4, MCSimExitCommand,
      "- exits the simulator" },
    { "go",       2, MCSimRunCommand,
      "[addr [n]] - sets the pc to addr and executes n instructions" },
    { "help",     4, MCSimHelpCommand,
      "[command] - prints the list of commands or help on a specific command" },
    { "interrupt",9, MCSimNotImplementedCommand,
      "[irq [t]] - set an interrupt on irq at time t (default is now)" },
    { "load",     4, MCSimLoadCommand,
      "filename - loads an S19 filename" },
    { "mc68state",9, MCSimMC68StateCommand,
      "- displays the contents of memory and break points." },
    { "quit",     4, MCSimExitCommand,
      "- exits the simulator" },
    { "registers",9, MCSimStateCommand,
      "- shows the contents of registers" },
    { "reset",    5, MCSimResetCommand,
      "- resets memory and registers, and reloads code" },
    { "setmem",   6, MCSimSetMemoryCommand,
      "addr val - sets the value of address addr to value val" },
    { "setreg",   6, MCSimSetRegisterCommand,
      "reg val - sets the d, x, or y register reg to the given value val" },
    { "showbreak",9, MCSimShowBreaksCommand,
      "- shows the break points" },
    { "step",     4, MCSimStepCommand,
      "[n] - steps n times" },
    { "tempbreak",9, MCSimTempBreakCommand,
      "[addr [n]] - sets a break at address addr (pc by default) to survive n hits" },
    { "version",  7, MCSimVersionCommand,
      "- displays the version of the simulator" },
    { "",         0, 0, "" }
};

MCSimCommandFunctions* MCSimSearchForFunction(
            const char* input, MCSimCommandFunctions* commandInfo) {
    MCSimCommandFunctions* simFunctionPtr = 0;
    if (commandInfo) {
      int commandLength = commandInfo->length;
      while (commandLength) {
        const char* commandName = commandInfo->name;
 
        /* Avoid the function call to strncmp when possible for speed */
        if ((*input == *commandName) &&
            (strncmp(input, commandName, commandLength) == 0) &&
            ((input[commandLength] == 0) || isspace(input[commandLength]))) {
          simFunctionPtr = commandInfo;
          break;
        }
        commandInfo++;
        commandLength = commandInfo->length;
      }
    }
    return simFunctionPtr;
}

/*
Prints out the list of commands, or help on a specific command.
*/
int MCSimHelpCommand(const char* input, MCSimState* mcsimptr) {
    MCSimCommandFunctions* commandInfo = MCSimCommandFunctionsList;
    MCSimCommandFunctions* funcCommandInfo =
        MCSimSearchForFunction(input, commandInfo);
    if ( funcCommandInfo ) {
      printf("%s %s\n", funcCommandInfo->name, funcCommandInfo->comment);
    }
    else {
      printf("commands={ ");
      while (commandInfo->length) {
        printf("%s ", commandInfo->name);
        commandInfo++;
      }
      printf("}\n");
    }
    fflush(stdout);
    return MC_SIM_NO_ERROR;
}

/*
Evaluates the user command and returns 0 on successful evaluation,
a negative number if the command requested to end the program, or
a positive error number if an error occurred.
*/
int MCSimEvaluateCommand(const char* input, MCSimState* mcsimptr) {
  int status = MC_SIM_NO_ERROR;
  MCSimCommandFunctions* commandInfo = MCSimCommandFunctionsList;
  if ( commandInfo ) {
    MCSimCommandFunctions* funcCommandInfo =
      MCSimSearchForFunction(input, commandInfo);
    if (funcCommandInfo) {
      const char* restOfArgs = &input[funcCommandInfo->length];
      while (*restOfArgs && isspace(*restOfArgs)) restOfArgs++;
      status = (*funcCommandInfo->function)(restOfArgs, mcsimptr);
    }
    else {
      status = MC_SIM_UNRECOGNIZED_COMMAND;
    }
  }
  else {
    status = MC_SIM_UNRECOGNIZED_BOARD;
  }
 
  return status;
}
