dmuck0.15-beta/docs/muf/
dmuck0.15-beta/game/
dmuck0.15-beta/game/logs/
dmuck0.15-beta/game/muf/
dmuck0.15-beta/game/muf/text/
#include "copyright.h"
#include "config.h"
#include "money.h"

#include "db.h"
#include "interface.h"
#include "externs.h"
#include "params.h"
#include "match.h"
#include <ctype.h>

extern char *lowercase, *uppercase;
#define DOWNCASE(x) (lowercase[x])

static char buf[BUFFER_LEN];

void editor(dbref player, char *command);
void do_insert(dbref player, dbref program, int arg[], int argc);
void do_delete(dbref player, dbref program, int arg[], int argc);
void do_quit(dbref player, dbref program);
void do_list(dbref player, dbref program, int arg[], int argc);
void insert(dbref player, char *line1);
line *get_new_line(void);
line *read_program(dbref i);
char *macro_expansion(macrotable *node, char *match);
void do_compile(dbref player, dbref program);
void free_line(line *l);
void free_prog_text(line *l);
void val_and_head(dbref player, int arg[], int argc);
void do_list_header(dbref player, dbref program);
void toggle_numbers(dbref player);

/* Editor routines --- Also contains routines to handle input */

/* This routine determines if a player is editing or running an interactive
   command.  It does it by checking the frame pointer field of the player ---
   if the program counter is NULL, then the player is not running anything
   The reason we don't just check the pointer but check the pc too is because
   I plan to leave the frame always on to save the time required allocating
   space each time a program is run.
   */
void interactive(dbref player, char *command)
{
  frame *fr;
  if ((fr = find_frame(DBFETCH(player)->sp.player.pid)) &&
    (fr->status == STATUS_READ) &&
    (fr->player == player) &&
    (DBFETCH(player)->curr_prog == NOTHING))
  {
    if (!string_compare(command, BREAK_COMMAND)) fr->status = STATUS_DEAD;
    else
    {
      if (fr->argument.top >= STACK_SIZE)
      {
        notify(player, player, "Program stack overflow.");
	fr->status = STATUS_DEAD;
        return;
      }
      fr->argument.st[fr->argument.top].type = PROG_STRING;
      fr->argument.st[fr->argument.top++].data.string = dup_string(command);
      fr->status = STATUS_RUN;
    }
  }
  else editor(player, command);
  DBDIRTY(player);
}

char *macro_expansion(macrotable *node, char *match)
{
  int value;
  if (!node) return NULL;
  else
  {
    value = string_compare(match, node->name);
    if (value < 0) return macro_expansion(node->left, match);
    else if (value > 0) return macro_expansion(node->right, match);
    else return dup_string (node->definition);
  }
}
macrotable *new_macro(char *name, char *definition,
  dbref player)
{
  macrotable *newmacro;
  int i;
  
  newmacro = (macrotable *)malloc (sizeof (macrotable));
  for (i = 0; name[i]; i++) buf[i] = DOWNCASE(name[i]);
  buf[i] = '\0';
  newmacro->name = dup_string(buf);
  newmacro->definition = dup_string(definition);
  newmacro->implementor = player;
  newmacro->left = NULL;
  newmacro->right = NULL;
  return (newmacro);
}

int grow_macro_tree(macrotable *node, macrotable *newmacro)
{
  int value;
  value = strcmp (newmacro->name, node->name);
  if (!value) return 0;
  else if (value < 0)
  {
    if (node->left) return grow_macro_tree (node->left, newmacro);
    else
    {
      node->left = newmacro;
      return 1;
    }
  }
  else if (node->right) return grow_macro_tree (node->right, newmacro);
  else
  {
    node->right = newmacro;
    return 1;
  }
}
void insert_macro(char *word[], dbref player)
{
  macrotable *newmacro;
  newmacro = new_macro (word[1], word[2], player);
  if (!macrotop) macrotop = newmacro;
  else if (!grow_macro_tree(macrotop, newmacro))
    notify (player, player, "Oopsie! That macro's already been defined.");
  else notify (player, player, "Entry created.");
}

void do_list_tree(macrotable *node, char *first, char *last,
  int length, dbref player)
{
  static char   buff[BUFSIZ];
  if (!node) return;
  else
  {
    if (strncmp(node->name, first, strlen(first)) >= 0)
      do_list_tree(node->left, first, last, length, player);
    if ((strncmp(node->name, first, strlen(first)) >= 0) &&
      (strncmp(node->name, last, strlen(last)) <= 0))
    {
      if (length)
      {
        sprintf(buff, "%-16s %-16s  %s", node->name,
          unparse_name(node->implementor),
          node->definition);
        notify(player, player, buff);
        buff[0] = '\0';
      }
      else
      {
        sprintf(buff + strlen(buff), "%-16s", node->name);
        if (strlen(buff) > 70)
	{
          notify(player, player, buff);
          buff[0] = '\0';
        }
      }
    }
    if (strncmp(last, node->name, strlen(last)) >= 0)
      do_list_tree(node->right, first, last, length, player);
    if ((node == macrotop) && !length)
    {
      notify(player, player, buff);
      buff[0] = '\0';
    }
  }
}
void list_macros(char *word[], int k, dbref player, int length)
{
  if (!k--) do_list_tree(macrotop, "a", "z", length, player);
  else do_list_tree(macrotop, word[0], word[k], length, player);
  notify(player, player, "End of list.");
  return;
}
int erase_node(macrotable *oldnode, macrotable *node,
  char *killname)
{
  if (!node) return 0;
  else if(strcmp(killname, node->name) < 0)
    return erase_node(node, node->left, killname);
  else if(strcmp(killname, node->name))
    return erase_node(node, node->right, killname);
  else
  {
    if (node == oldnode->left)
    {
      oldnode->left = node->left;
      if (node->right) grow_macro_tree (macrotop, node->right);
      free ((void *) node);
      return 1;
    }
    else
    {
      oldnode->right = node->right;
      if (node->left) grow_macro_tree (macrotop, node->left);
      free ((void *) node);
      return 1;
    }
  }
}


void kill_macro(char *word[], dbref player)
{
  if (!Wizard(player))
  {
    notify (player, player, "I'm sorry, Dave, I can't let you do that.");
    return;
  }
  else if (!macrotop)
  {
    notify (player, player, "You've got nothing and you want to kill? Sheesh!");
    return;
  }
  else if (!string_compare(word[0], macrotop->name))
  {
    macrotable *macrotemp = macrotop;
    int whichway = (macrotop->left) ? 1 : 0;
    macrotop = whichway ? macrotop->left : macrotop->right;
    if (macrotop && (whichway ? macrotemp->right : macrotemp->left))
      grow_macro_tree(macrotop, whichway ?
        macrotemp->right : macrotemp->left);
    free ((void *) macrotemp);
    notify (player, player, "Entry removed.");
  }
  else if (erase_node(macrotop, macrotop, word[0]))
    notify (player, player, "Entry removed.");
  else notify (player, player, "Entry to remove not found.");
}


/* The editor itself --- this gets called each time every time to
 * parse a command.
 */

void editor(dbref player, char *command)
{
  dbref  program;
  int    arg[MAX_ARG+1];
  char   buff[BUFFER_LEN];
   char   *word[MAX_ARG+1];
  int    i, j;           /* loop variables */

  program = DBFETCH(player)->curr_prog;

  /* check to see if we are insert mode */
  if (DBFETCH(player)->sp.player.insert_mode)
  {
    insert(player, command);             /* insert it! */
    return;
  }
  /* parse the commands */
  for (i = 0; i <= MAX_ARG && *command; i++)
  {
    while (*command && isspace(*command)) command++;
    j = 0;
    while (*command && !isspace(*command))
    {
      buff[j] = *command;
      command++, j++;
    }
     
    buff[j] = '\0';
    word[i] = dup_string(buff);
    if ((i == 1) && !string_compare(word[0], "def"))
    {
      while (*command && isspace(*command)) command++;
      word[2] = dup_string(command);
      if (!word[2]) notify (player, player, "Invalid definition syntax.");
      else insert_macro(word, player);
      for (; i >= 0; i--)
      {
        if (word[i]) free ((void *) word[i]);
      }
      return;
    }
    arg[i] = atoi(buff);
    if (arg[i] < 0)
    {
      notify(player, player, "Negative arguments not allowed!");
      for (; i >= 0; i--)
      {
        if (word[i]) free ((void *) word[i]);
      }
      return;
    }
  }
  i--;
  while ((i >= 0) && !word[i]) i--;
  if (i < 0) return;
  else
  {
    switch (word[i][0])
    {
      case KILL_COMMAND:
        kill_macro(word, player);
        break;
      case SHOW_COMMAND:
        list_macros(word, i, player, 1);
        break;
      case SHORTSHOW_COMMAND:
        list_macros(word, i, player, 0);
        break;
      case INSERT_COMMAND:
        notify(player, player, "Entering insert mode.");
        do_insert(player, program, arg, i);
        break;
      case DELETE_COMMAND:
        do_delete(player, program, arg, i);
        break;
      case QUIT_EDIT_COMMAND:
        do_quit(player, program);
        notify(player, player, "Editor exited.");
        break;
      case COMPILE_COMMAND:
        /* compile code belongs in compile.c, not in the editor */
        do_compile(player, program);
        notify(player, player, "Compiler done.");
        break;
      case LIST_COMMAND:
        do_list(player, program, arg, i);
        break;
      case EDITOR_HELP_COMMAND:
        spit_file(player, EDITOR_HELP_FILE);
        break;
      case VIEW_COMMAND:
        val_and_head(player, arg, i);
        break;
      case UNASSEMBLE_COMMAND:
        disassemble(player, program);
        break;
      case NUMBER_COMMAND:
        toggle_numbers(player);
        break;
      default:
        notify(player, player, "Illegal editor command.");
        break;
    }
  }
  for (; i >= 0; i--)
  {
    if (word[i]) free ((void *) word[i]);
  }
}

/* puts program into insert mode */
void do_insert(dbref player, dbref program, int arg[], int argc)
{
  DBFETCH(player)->sp.player.insert_mode++;
  DBDIRTY(player);
  if (argc) DBSTORE(program, sp.program.curr_line, arg[0] - 1);
    /* set current line to something else */
  if (FLAGS(player) & INTERNAL)
  {
    sprintf (buf, "%d>", DBFETCH(program)->sp.program.curr_line + 1);
    notify (player, player, buf);
  }
}

/* deletes line n if one argument,
   lines arg1 -- arg2 if two arguments
   current line if no argument */
void do_delete(dbref player, dbref program, int arg[], int argc)
{
  line   *curr, *temp;
  char          buff[BUFFER_LEN];
  int   i;
  
  switch (argc)
  {
    case 0:
      arg[0] = DBFETCH(program)->sp.program.curr_line;
    case 1:
      arg[1] = arg[0];
    case 2:
      /* delete from line 1 to line 2 */
      /* first, check for conflict */
      if (arg[0] > arg[1])
      {
        notify(player, player, "Nonsensical arguments.");
        return;
      }
      i = arg[0] - 1;
      for (curr = DBFETCH(program)->sp.program.first; curr && i; i--)
        curr = curr->next;
      if (curr)
      {
        DBFETCH(program)->sp.program.curr_line = arg[0];
        i = arg[1] - arg[0] + 1;
          /* delete n lines */
        while (i && curr)
        {
          temp = curr;
          if (curr->prev) curr->prev->next = curr->next;
          else DBFETCH(program)->sp.program.first = curr->next;
          if (curr->next) curr->next->prev = curr->prev;
          curr = curr->next;
          free_line(temp);
          i--;
        }
        sprintf(buff, "%d lines deleted", arg[1] - arg[0] - i + 1);
        notify(player, player, buff);
      }
      else notify(player, player, "No line to delete!");
      break;
    default:
      notify(player, player, "Too many arguments!");
      break;
  }
}

/* quit from edit mode.  Put player back into the regular game mode */
void do_quit(dbref player, dbref program)
{
  write_program(DBFETCH(program)->sp.program.first, program);
  free_prog_text(DBFETCH(program)->sp.program.first);
  DBSTORE(program, sp.program.editlocks,
    dbreflist_remove(DBFETCH(program)->sp.program.editlocks, player));
  FLAGS(player) &= ~INTERACTIVE;
  DBSTORE(player, curr_prog, NOTHING);
  DBDIRTY(player);
  DBDIRTY(program);
}

void match_and_list(dbref player, char *name, char *linespec)
{
  dbref thing;
  char *p;
  char *q;
  int range[2];
  int argc;
  match_data md;
  
 if(Typeof(player) != TYPE_PLAYER) {
    notify(OWNER(player), OWNER(player), "Only Players can list programs.");
    return;
 }

  init_match(player, name, TYPE_PROGRAM, &md);
  match_neighbor(&md);
  match_possession(&md);
  match_absolute(&md);
  if ((thing = noisy_match_result(&md)) == NOTHING) return;
  if (Typeof(thing) != TYPE_PROGRAM)
  {
    notify(player, player, "You can't list anything but a program.");
    return;
  }
  if (!(controls(player, thing) || Linkable(thing)))
  {
    notify(player, player, "Permission denied.");
    return;
  }
  
  if (DBFETCH(thing)->sp.program.editlocks != NULL)
  {
    notify(player, player, "Sorry, that program is being edited.");
    return;
  }

  if (!*linespec)
  {
    range[0] = 1;
    range[1] = -1;
    argc = 2;
  }
  else
  {
    q = p = linespec;
    while(*p)
    {
      while(*p && !isspace(*p)) *q++ = *p++;
      while(*p && isspace(*++p));
    }
    *q = '\0';

    argc = 1;
    if (isdigit(*linespec))
    {
      range[0] = atoi(linespec);
      while(*linespec && isdigit(*linespec)) linespec++;
    }
    else range[0] = 1;
    if (*linespec)
    {
      argc = 2;
      while(*linespec && !isdigit(*linespec)) linespec++;
      if (*linespec) range[1] = atoi(linespec);
      else range[1] = -1;
    }
  }
  DBSTORE(thing, sp.program.first, read_program(thing));
  do_list(player, thing, range, argc);
  if (!(FLAGS(thing) & INTERNAL))
    free_prog_text(DBFETCH(thing)->sp.program.first);
  return;
}

/* list --- if no argument, redisplay the current line
   if 1 argument, display that line
   if 2 arguments, display all in between   */
void do_list(dbref player, dbref program, int oarg[], int argc)
{
  line *curr;
  int   i, count;
  int arg[2];
  char  buff[BUFFER_LEN];
  
  if (oarg)
  {
    arg[0] = oarg[0];
    arg[1] = oarg[1];
  }
  else arg[0] = arg[1] = 0;
  switch (argc)
  {
    case 0:
      arg[0] = DBFETCH(program)->sp.program.curr_line;
    case 1:
      arg[1] = arg[0];
    case 2:
      if ((arg[0] > arg[1]) && (arg[1] != -1))
      {
        notify(player, player, "Arguments don't make sense!");
        return;
      }
      i = arg[0] - 1;
      for (curr = DBFETCH(program)->sp.program.first;
	i && curr;
	i--, curr = curr->next);
      if (curr)
      {
        i = arg[1] - arg[0] + 1;
        /* display n lines */
        for (count = arg[0]; curr && (i || (arg[1] == -1)); i--)
        {
          if (FLAGS(player) & INTERNAL)
            sprintf(buff, "%3d: %s", count, DoNull(curr->this_line));
          else sprintf(buff, "%s", DoNull(curr->this_line));
          notify(player, player, buff);
          count++;
          curr = curr->next;
        }
        if (count - arg[0] > 1)
	{
          sprintf(buff, "%d lines displayed.", count - arg[0]);
          notify(player, player, buff);
        }
      }
      else notify(player, player, "Line not available for display.");
      break;
    default:
      notify(player, player, "Too many arguments!");
      break;
  }
}

void val_and_head(dbref player, int arg[], int argc)
{
  dbref program;
  
  if (argc != 1)
  {
    notify(player, player,
      "I don't understand which header you're trying to look at.");
    return;
  }
  program = arg[0];
  if (program < 0 || program >= db_top || Typeof(program) != TYPE_PROGRAM)
  {
    notify(player, player, "That isn't a program.");
    return;
  }
  if (!(controls(player, program) || Linkable(program)))
  {
    notify(player, player, "That's not a public program.");
    return;
  }
  do_list_header(player, program);
}

void do_list_header(dbref player, dbref program)
{
  line *curr = read_program(program);
  
  while (curr && (curr->this_line)[0] == '(')  
  {
    notify (player, player, curr->this_line);
    curr = curr->next;
  }
  if (!(FLAGS(program) & INTERNAL)) free_prog_text(curr);
  notify (player, player, "Done.");
}

void toggle_numbers(dbref player)
{
  if (FLAGS(player) & INTERNAL)
  {
    FLAGS(player) &= ~INTERNAL;
    notify(player, player, "Line numbers off.");
  }
  else
  {
    FLAGS(player) |= INTERNAL;
    notify(player, player, "Line numbers on.");
  }
}

/* insert this line into program */
void insert(dbref player, char *line_text)
{
  dbref program;
  int  i;
  line *curr;
  line *new_line;

  program = DBFETCH(player)->curr_prog;
  if (!string_compare(line_text, EXIT_INSERT))
  {
    DBSTORE(player, sp.player.insert_mode, 0);   /* turn off insert mode */
    return;
  }
  i = DBFETCH(program)->sp.program.curr_line - 1;
  for (curr = DBFETCH(program)->sp.program.first;
    curr && i && i + 1;
    i--, curr = curr->next);
  new_line = get_new_line();         /* initialize line */
  new_line->this_line = dup_string(line_text);
  if (!DBFETCH(program)->sp.program.first)
  /* nothing --- insert in front */
  {
    DBFETCH(program)->sp.program.first = new_line;
    DBFETCH(program)->sp.program.curr_line  = 2;        /* insert at the end */
    DBDIRTY(program);
    if (FLAGS(player) & INTERNAL)
    {
      sprintf (buf, "%d>", DBFETCH(program)->sp.program.curr_line);
      notify (player, player, buf);
    }
    return;
  }
  if (!curr)                 /* insert at the end */
  {
    i = 1;
    for (curr = DBFETCH(program)->sp.program.first;
      curr->next;
      curr = curr->next, i++);
    DBFETCH(program)->sp.program.curr_line = i + 2;
    new_line->prev = curr;
    curr->next = new_line;
    DBDIRTY(program);
    if (FLAGS(player) & INTERNAL)
    {
      sprintf (buf, "%d>", DBFETCH(program)->sp.program.curr_line);
      notify (player, player, buf);
    }
    return;
  }
  if (!DBFETCH(program)->sp.program.curr_line)   /* insert at the beginning */
  {
    DBFETCH(program)->sp.program.curr_line = 1; /* insert after this new line */
    new_line->next = DBFETCH(program)->sp.program.first;
    DBFETCH(program)->sp.program.first = new_line;
    DBDIRTY(program);
    if (FLAGS(player) & INTERNAL)
    {
      sprintf (buf, "%d>", DBFETCH(program)->sp.program.curr_line + 1);
      notify (player, player, buf);
    }
    return;
  }
  /* inserting in the middle */
  DBFETCH(program)->sp.program.curr_line++;
  if (FLAGS(player) & INTERNAL)
  {
    sprintf (buf, "%d>", DBFETCH(program)->sp.program.curr_line + 1);
    notify (player, player, buf);
  }
  new_line->prev = curr;
  new_line->next = curr->next;
  if (new_line->next) new_line->next->prev = new_line;
  curr->next = new_line;
  DBDIRTY(program);
}