lpmud/mudlib/
lpmud/mudlib/doc/
lpmud/mudlib/doc/LPC/
lpmud/mudlib/log/
lpmud/mudlib/players/
lpmud/mudlib/room/maze1/
lpmud/mudlib/room/sub/
/*
 *  ed - standard editor
 *  ~~
 *  Authors: Brian Beattie, Kees Bot, and others
 *
 * Copyright 1987 Brian Beattie Rights Reserved.
 * Permission to copy or distribute granted under the following conditions:
 * 1). No charge may be made other than reasonable charges for reproduction.
 * 2). This notice must remain intact.
 * 3). No further restrictions may be added.
 * 4). Except meaningless ones.
 *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 *  TurboC mods and cleanup 8/17/88 RAMontante.
 *  Further information (posting headers, etc.) at end of file.
 *  RE stuff replaced with Spencerian version, sundry other bugfix+speedups
 *  Ian Phillipps. Version incremented to "5".
 * _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
 */

int version = 5;                /* used only in the "set" function, for i.d. */

#include "os.h"
/* Regexp is Henry Spencer's package. WARNING: regsub is modified to return
 * a pointer to the \0 after the destination string, and this program refers
 * to the "private" reganch field in the struct regexp.
 */
#include "regexp.h"
#include "object.h"
#include "config.h"

/*
 *  #defines for non-printing ASCII characters
 */
#define NUL 0x00                /* ^@ */
#define EOS 0x00                /* end of string */
#define SOH 0x01                /* ^A */
#define STX 0x02                /* ^B */
#define ETX 0x03                /* ^C */
#define EOT 0x04                /* ^D */
#define ENQ 0x05                /* ^E */
#define ACK 0x06                /* ^F */
#define BEL 0x07                /* ^G */
#define BS  0x08                /* ^H */
#define HT  0x09                /* ^I */
#define LF  0x0a                /* ^J */
#define NL  '\n'
#define VT  0x0b                /* ^K */
#define FF  0x0c                /* ^L */
#define CR  0x0d                /* ^M */
#define SO  0x0e                /* ^N */
#define SI  0x0f                /* ^O */
#define DLE 0x10                /* ^P */
#define DC1 0x11                /* ^Q */
#define DC2 0x12                /* ^R */
#define DC3 0x13                /* ^S */
#define DC4 0x14                /* ^T */
#define NAK 0x15                /* ^U */
#define SYN 0x16                /* ^V */
#define ETB 0x17                /* ^W */
#define CAN 0x18                /* ^X */
#define EM  0x19                /* ^Y */
#define SUB 0x1a                /* ^Z */
#define ESC 0x1b                /* ^[ */
#define FS  0x1c                /* ^\ */
#define GS  0x1d                /* ^] */
#define RS  0x1e                /* ^^ */
#define US  0x1f                /* ^_ */
#define SP  0x20                /* space */
#define DEL 0x7f                /* DEL */
#define ESCAPE  '\\'


#define TRUE    1
#define FALSE   0
#define ERR     -2
#define FATAL       (ERR-1)
#define CHANGED     (ERR-2)
#define SET_FAIL    (ERR-3)
#define SUB_FAIL    (ERR-4)
#define MEM_FAIL    (ERR-5)


#define BUFFER_SIZE 2048        /* stream-buffer size:  == 1 hd cluster */

#define LINFREE 1               /* entry not in use */
#define LGLOB   2               /* line marked global */

#define MAXLINE 512             /* max number of chars per line */
#define MAXPAT  256             /* max number of chars per replacement pattern */
#define MAXFNAME 256            /* max file name size */


/**  Global variables  **/

struct line {
  int l_stat;                   /* empty, mark */
  struct line *l_prev;
  struct line *l_next;
  char l_buff[1];
};
typedef struct line LINE;

extern struct object *command_giver;

/* comm1.c */
extern void set_prompt(char *str);
extern void add_message(char *fmt, ...);

/* simulate.c */
extern void error(char *fmt, ...);
extern char *check_file_name(char *file, int writeflg);

/* main.c */
extern void *xalloc(int size);
extern char *escape_path (char *str);

#define P_DIAG      (command_giver->ed_buffer->diag)
#define P_TRUNCFLG  (command_giver->ed_buffer->truncflg)
#define P_EIGHTBIT  (command_giver->ed_buffer->eightbit)
#define P_NONASCII  (command_giver->ed_buffer->nonascii)
#define P_NULLCHAR  (command_giver->ed_buffer->nullchar)
#define P_TRUNCATED (command_giver->ed_buffer->truncated)
#define P_FNAME     (command_giver->ed_buffer->fname)
#define P_FCHANGED  (command_giver->ed_buffer->fchanged)
#define P_NOFNAME   (command_giver->ed_buffer->nofname)
#define P_MARK      (command_giver->ed_buffer->mark)
#define P_OLDPAT    (command_giver->ed_buffer->oldpat)
#define P_LINE0     (command_giver->ed_buffer->Line0)
#define P_LINE0     (command_giver->ed_buffer->Line0)
#define P_CURLN     (command_giver->ed_buffer->CurLn)
#define P_CURPTR    (command_giver->ed_buffer->CurPtr)
#define P_LASTLN    (command_giver->ed_buffer->LastLn)
#define P_LINE1     (command_giver->ed_buffer->Line1)
#define P_LINE2     (command_giver->ed_buffer->Line2)
#define P_NLINES    (command_giver->ed_buffer->nlines)
#define P_PFLAG     (command_giver->ed_buffer->pflag)
#define P_NFLG      (command_giver->ed_buffer->nflg)
#define P_LFLG      (command_giver->ed_buffer->lflg)
#define P_PFLG      (command_giver->ed_buffer->pflg)
#define P_APPENDING (command_giver->ed_buffer->appending)

char inlin[MAXLINE];
char *inptr;                    /* tty input buffer */
struct ed_buffer {
  int diag;                     /* diagnostic-output? flag */
  int truncflg;                 /* truncate long line flag */
  int eightbit;                 /* save eighth bit */
  int nonascii;                 /* count of non-ascii chars read */
  int nullchar;                 /* count of null chars read */
  int truncated;                /* count of lines truncated */
  char fname[MAXFNAME];
  int fchanged;                 /* file-changed? flag */
  int nofname;
  int mark['z' - 'a' + 1];
  regexp *oldpat;

  LINE Line0;
  int CurLn;
  LINE *CurPtr;                 /* CurLn and CurPtr must be kept in step */
  int LastLn;
  int pflag;
  int Line1, Line2, nlines;
  int nflg;                     /* print line number flag */
  int lflg;                     /* print line in verbose mode */
  int pflg;                     /* print current line after each command */
  int appending;
};

#if 0

struct tbl {
  char *t_str;
  int *t_ptr;
  int t_val;
} *t, tbl[] = {
"number", &nflg, TRUE,
    "nonumber", &nflg, FALSE,
    "list", &lflg, TRUE,
    "nolist", &lflg, FALSE,
    "eightbit", &eightbit, TRUE, "noeightbit", &eightbit, FALSE, 0};
#endif


/*-------------------------------------------------------------------------*/

/* ed.c */
static int append (int line, int glob);
static int more_append (char *str);
static int ckglob (void);
static int deflt (int def1, int def2);
static int del (int from, int to);
static int dolst (int line1, int line2);
static int esc (char **s);
static int doprnt (int from, int to);
static void prntln (char *str, int vflg, int lin);
static void putcntl (char c, FILE * stream);
static int egets (char *str, int size, FILE * stream);
static int doread (int lin, char *fname);
static int dowrite (int from, int to, char *fname, int apflg);
static int find (regexp * pat, int dir);
static char *getfn (int writeflg);
static int getnum (int first);
static int getone (void);
static int getlst (void);
static LINE *getptr (int num);
static int getrhs (char *sub);
static int ins (char *str);
static int join (int first, int last);
static int move (int num);
static int transfer (int num);
static regexp *optpat (void);
void regerror (char *s);
static int set (void);
static void set_ed_buf (void);
static int subst (regexp * pat, char *sub, int gflg, int pflag);
static int docmd (int glob);
static int doglob (void);

void ed_start (char *file_arg);
void ed_cmd (char *str);


/*________  Macros  ________________________________________________________*/

#ifndef max
#  define max(a,b)  ((a) > (b) ? (a) : (b))
#endif

#ifndef min
#  define min(a,b)  ((a) < (b) ? (a) : (b))
#endif

#ifndef toupper
#  define toupper(c)    ((c >= 'a' && c <= 'z') ? c-32 : c )
#endif

#define nextln(l)   ((l)+1 > P_LASTLN ? 0 : (l)+1)
#define prevln(l)   ((l)-1 < 0 ? P_LASTLN : (l)-1)

#define gettxtl(lin)    ((lin)->l_buff)
#define gettxt(num) (gettxtl( getptr(num) ))

#define getnextptr(p)   ((p)->l_next)
#define getprevptr(p)   ((p)->l_prev)

#define setCurLn( lin ) ( P_CURPTR = getptr( P_CURLN = (lin) ) )
#define nextCurLn() ( P_CURLN = nextln(P_CURLN), P_CURPTR = getnextptr( P_CURPTR ) )
#define prevCurLn() ( P_CURLN = prevln(P_CURLN), P_CURPTR = getprevptr( P_CURPTR ) )

#define clrbuf()    del(1, P_LASTLN)

#define Skip_White_Space    {while (*inptr==SP || *inptr==HT) inptr++;}

#define relink(a, x, y, b) { (x)->l_prev = (a); (y)->l_next = (b); }


/*________  functions  ______________________________________________________*/


/*  append.c    */


static int append (int line, int glob)
{
  if (glob)
    return (ERR);
  setCurLn (line);
  P_APPENDING = 1;
  set_prompt ("*\b");
  return 0;
}

static int more_append (char *str)
{
  if (P_NFLG)
    add_message ("%6d. ", P_CURLN + 1);
  if (str[0] == '.' && str[1] == '\0') {
    P_APPENDING = 0;
    set_prompt (":");
    return (0);
  }
  if (ins (str) < 0)
    return (MEM_FAIL);
  return 0;
}

/*  ckglob.c    */
static int ckglob (void)
{
  regexp *glbpat;
  char c, delim, *lin;
  int num;
  LINE *ptr;

  c = *inptr;

  if (c != 'g' && c != 'v')
    return (0);
  if (deflt (1, P_LASTLN) < 0)
    return (ERR);

  delim = *++inptr;
  if (delim <= ' ')
    return (ERR);

  glbpat = optpat ();
  if (*inptr == delim)
    inptr++;
  ptr = getptr (1);
  for (num = 1; num <= P_LASTLN; num++) {
    ptr->l_stat &= ~LGLOB;
    if (P_LINE1 <= num && num <= P_LINE2) {
      lin = gettxtl (ptr);
      if (regexec (glbpat, lin)) {
        if (c == 'g')
          ptr->l_stat |= LGLOB;
      } else {
        if (c == 'v')
          ptr->l_stat |= LGLOB;
      }
      ptr = getnextptr (ptr);
    }
  }
  return (1);
}


/*  deflt.c
 *  Set P_LINE1 & P_LINE2 (the command-range delimiters) if the file is
 *  empty; Test whether they have valid values.
 */
static int deflt (int def1, int def2)
{
  if (P_NLINES == 0) {
    P_LINE1 = def1;
    P_LINE2 = def2;
  }
  return ((P_LINE1 > P_LINE2 || P_LINE1 <= 0) ? ERR : 0);
}


/*  del.c   */

/* One of the calls to this function tests its return value for an error
 * condition.  But del doesn't return any error value, and it isn't obvious
 * to me what errors might be detectable/reportable.  To silence a warning
 * message, I've added a constant return statement. -- RAM
 * ... It could check to<=P_LASTLN ... igp
 */
static int del (int from, int to)
{
  LINE *first, *last, *next, *tmp;

  if (from < 1)
    from = 1;
  first = getprevptr (getptr (from));
  last = getnextptr (getptr (to));
  next = first->l_next;
  while (next != last && next != &P_LINE0) {
    tmp = next->l_next;
    free (next);
    next = tmp;
  }
  relink (first, last, first, last);
  P_LASTLN -= (to - from) + 1;
  setCurLn (prevln (from));
  return (0);
}


static int dolst (int line1, int line2)
{
  int oldlflg = P_LFLG, p;

  P_LFLG = 1;
  p = doprnt (line1, line2);
  P_LFLG = oldlflg;
  return p;
}


/*  esc.c
 * Map escape sequences into their equivalent symbols.  Returns the
 * correct ASCII character.  If no escape prefix is present then s
 * is untouched and *s is returned, otherwise **s is advanced to point
 * at the escaped character and the translated character is returned.
 */
static int esc (char **s)
{
  register int rval;

  if (**s != ESCAPE) {
    rval = **s;
  } else {
    (*s)++;
    switch (toupper (**s)) {
    case '\000':
      rval = ESCAPE;
      break;
    case 'S':
      rval = ' ';
      break;
    case 'N':
      rval = '\n';
      break;
    case 'T':
      rval = '\t';
      break;
    case 'B':
      rval = '\b';
      break;
    case 'R':
      rval = '\r';
      break;
    default:
      rval = **s;
      break;
    }
  }
  return (rval);
}


/*  doprnt.c    */
static int doprnt (int from, int to)
{
  from = (from < 1) ? 1 : from;
  to = (to > P_LASTLN) ? P_LASTLN : to;

  if (to != 0) {
    setCurLn (from);
    while (P_CURLN <= to) {
      prntln (gettxtl (P_CURPTR), P_LFLG, (P_NFLG ? P_CURLN : 0));
      if (P_CURLN == to)
        break;
      nextCurLn ();
    }
  }
  return (0);
}


static void prntln (char *str, int vflg, int lin)
{
  if (lin)
    add_message ("%7d ", lin);
  while (*str && *str != NL) {
    if (*str < ' ' || *str >= 0x7f) {
      switch (*str) {
      case '\t':
        if (vflg)
          putcntl (*str, stdout);
        else
          add_message ("%c", *str);
        break;

      case DEL:
        putc ('^', stdout);
        putc ('?', stdout);
        break;

      default:
        putcntl (*str, stdout);
        break;
      }
    } else
      add_message ("%c", *str);
    str++;
  }
  if (vflg)
    add_message ("$");
  add_message ("\n");
}


static void putcntl (char c, FILE * stream)
{
  putc ('^', stream);
  putc ((c & 31) | '@', stream);
}


/*  egets.c */
static int egets (char *str, int size, FILE * stream)
{
  int c = 0, count;
  char *cp;

  for (count = 0, cp = str; size > count;) {
    c = getc (stream);
    if (c == EOF) {
      *cp = EOS;
      if (count)
        add_message ("[Incomplete last line]\n");
      return (count);
    } else if (c == NL) {
      *cp = EOS;
      return (++count);
    } else if (c == 0)
      P_NULLCHAR++;             /* count nulls */
    else {
      if (c > 127) {
        if (!P_EIGHTBIT)        /* if not saving eighth bit */
          c = c & 127;          /* strip eigth bit */
        P_NONASCII++;           /* count it */
      }
      *cp++ = c;                /* not null, keep it */
      count++;
    }
  }
  str[count - 1] = EOS;
  if (c != NL) {
    add_message ("truncating line\n");
    P_TRUNCATED++;
    while ((c = getc (stream)) != EOF)
      if (c == NL)
        break;
  }
  return (count);
}                               /* egets */


static int doread (int lin, char *fname)
{
  FILE *fp;
  int err;
  unsigned long bytes;
  unsigned int lines;
  static char str[MAXLINE];

  P_NONASCII = P_NULLCHAR = P_TRUNCATED = 0;

  if (P_DIAG)
    add_message ("\"%s\" ", fname);
  escape_path (fname);
  if ((fp = fopen (fname, "r")) == NULL) {
    add_message (" isn't readable.\n");
    return (ERR);
  }
  setCurLn (lin);
  for (lines = 0, bytes = 0; (err = egets (str, MAXLINE, fp)) > 0;) {
    bytes += err;
    if (ins (str) < 0) {
      err = MEM_FAIL;
      break;
    }
    lines++;
  }
  fclose (fp);
  if (err < 0)
    return (err);
  if (P_DIAG) {
    add_message ("%u lines %u bytes", lines, bytes);
    if (P_NONASCII)
      add_message (" [%d non-ascii]", P_NONASCII);
    if (P_NULLCHAR)
      add_message (" [%d nul]", P_NULLCHAR);
    if (P_TRUNCATED)
      add_message (" [%d lines truncated]", P_TRUNCATED);
    add_message ("\n");
  }
  return (err);
}                               /* doread */


static int dowrite (int from, int to, char *fname, int apflg)
{
  FILE *fp;
  int lin, err;
  unsigned int lines;
  unsigned long bytes;
  char *str;
  LINE *lptr;

  err = 0;
  lines = bytes = 0;

  add_message ("\"%s\" ", fname);
  escape_path (fname);
  if ((fp = fopen (fname, (apflg ? "a" : "w"))) == NULL) {
    add_message (" can't be opened for writing!\n");
    return (ERR);
  }

  lptr = getptr (from);
  for (lin = from; lin <= to; lin++) {
    str = lptr->l_buff;
    lines++;
    bytes += strlen (str) + 1;  /* str + '\n' */
    if (fputs (str, fp) == EOF) {
      add_message ("file write error\n");
      err++;
      break;
    }
    fputc ('\n', fp);
    lptr = lptr->l_next;
  }
  add_message ("%u lines %lu bytes\n", lines, bytes);
  fclose (fp);
  return (err);
}                               /* dowrite */


/*  find.c  */
static int find (regexp * pat, int dir)
{
  int i, num;
  LINE *lin;

  num = P_CURLN;
  lin = P_CURPTR;
  for (i = 0; i < P_LASTLN; i++) {
    if (regexec (pat, gettxtl (lin)))
      return (num);
    if (dir)
      num = nextln (num), lin = getnextptr (lin);
    else
      num = prevln (num), lin = getprevptr (lin);
  }
  return (ERR);
}


/*  getfn.c */
static char *getfn (int writeflg)
{
  static char file[256];
  char *cp;

  if (*inptr == NL) {
    P_NOFNAME = TRUE;
    strcpy (file, P_FNAME);
  } else {
    char *file2;
    P_NOFNAME = FALSE;
    Skip_White_Space;

    cp = file;
    while (*inptr && *inptr != NL && *inptr != SP && *inptr != HT)
      *cp++ = *inptr++;
    *cp = '\0';

    if (strlen (file) == 0) {
      add_message ("bad file name\n");
      return (NULL);
    }
    file2 = check_file_name (file, writeflg);
    if (!file2)
      return (NULL);
    strcpy (file, file2);
  }

  if (strlen (file) == 0) {
    add_message ("no file name\n");
    return (NULL);
  }
  return (file);
}                               /* getfn */


static int getnum (int first)
{
  regexp *srchpat;
  int num;
  char c;

  Skip_White_Space;

  if (*inptr >= '0' && *inptr <= '9') { /* line number */
    for (num = 0; *inptr >= '0' && *inptr <= '9'; ++inptr) {
      num = (num * 10) + (*inptr - '0');
    }
    return num;
  }

  switch (c = *inptr) {
  case '.':
    inptr++;
    return (P_CURLN);

  case '$':
    inptr++;
    return (P_LASTLN);

  case '/':
  case '?':
    srchpat = optpat ();
    if (*inptr == c)
      inptr++;
    return (find (srchpat, c == '/' ? 1 : 0));

  case '-':
  case '+':
    return (first ? P_CURLN : 1);

  case '\'':
    inptr++;
    if (*inptr < 'a' || *inptr > 'z')
      return (EOF);
    return P_MARK[*inptr++ - 'a'];

  default:
    return (first ? EOF : 1);   /* unknown address */
  }
}                               /* getnum */


/*  getone.c
 *  Parse a number (or arithmetic expression) off the command line.
 */
#define FIRST 1
#define NOTFIRST 0

static int getone (void)
{
  int c, i, num;

  if ((num = getnum (FIRST)) >= 0) {
    for (;;) {
      Skip_White_Space;
      if (*inptr != '+' && *inptr != '-')
        break;                  /* exit infinite loop */

      c = *inptr++;
      if ((i = getnum (NOTFIRST)) < 0)
        return (i);
      if (c == '+')
        num += i;
      else
        num -= i;
    }
  }
  return (num > P_LASTLN ? ERR : num);
}                               /* getone */


static int getlst (void)
{
  int num;

  P_LINE2 = 0;
  for (P_NLINES = 0; (num = getone ()) >= 0;) {
    P_LINE1 = P_LINE2;
    P_LINE2 = num;
    P_NLINES++;
    if (*inptr != ',' && *inptr != ';')
      break;
    if (*inptr == ';')
      setCurLn (num);
    inptr++;
  }
  P_NLINES = min (P_NLINES, 2);
  if (P_NLINES == 0)
    P_LINE2 = P_CURLN;
  if (P_NLINES <= 1)
    P_LINE1 = P_LINE2;

  return ((num == ERR) ? num : P_NLINES);
}                               /* getlst */


/*  getptr.c    */
static LINE *getptr (int num)
{
  LINE *ptr;
  int j;

  if (2 * num > P_LASTLN && num <= P_LASTLN) {  /* high line numbers */
    ptr = P_LINE0.l_prev;
    for (j = P_LASTLN; j > num; j--)
      ptr = ptr->l_prev;
  } else {                      /* low line numbers */
    ptr = &P_LINE0;
    for (j = 0; j < num; j++)
      ptr = ptr->l_next;
  }
  return (ptr);
}


/*  getrhs.c    */
static int getrhs (char *sub)
{
  char delim = *inptr++;
  char *outmax = sub + MAXPAT;
  if (delim == NL || *inptr == NL)      /* check for eol */
    return (ERR);
  while (*inptr != delim && *inptr != NL) {
    if (sub > outmax)
      return ERR;
    if (*inptr == ESCAPE) {
      switch (*++inptr) {
      case 'r':
        *sub++ = '\r';
        inptr++;
        break;
      case ESCAPE:
        *sub++ = ESCAPE;
        *sub++ = ESCAPE;
        inptr++;
      case 'n':
        *sub++ = '\n';
        inptr++;
        break;
      case 'b':
        *sub++ = '\b';
        inptr++;
        break;
      case '0':{
          int i = 3;
          *sub = 0;
          do {
            if (*++inptr < '0' || *inptr > '7')
              break;
            *sub = (*sub << 3) | (*inptr - '0');
          } while (--i != 0);
          sub++;
        }
        break;
      default:
        if (*inptr != delim)
          *sub++ = ESCAPE;
        *sub++ = *inptr;
        if (*inptr != NL)
          inptr++;
      }
    } else
      *sub++ = *inptr++;
  }
  *sub = '\0';

  inptr++;                      /* skip over delimter */
  Skip_White_Space;
  if (*inptr == 'g') {
    inptr++;
    return (1);
  }
  return (0);
}

/*  ins.c   */
static int ins (char *str)
{
  char *cp;
  LINE *new, *nxt;
  int len;

  do {
    for (cp = str; *cp && *cp != NL; cp++);
    len = cp - str;
    /* cp now points to end of first or only line */

    if ((new = (LINE *) xalloc (sizeof (LINE) + len)) == NULL)
      return (MEM_FAIL);        /* no memory */

    new->l_stat = 0;
    strncpy (new->l_buff, str, len);    /* build new line */
    new->l_buff[len] = EOS;
    nxt = getnextptr (P_CURPTR);        /* get next line */
    relink (P_CURPTR, new, new, nxt);   /* add to linked list */
    relink (new, nxt, P_CURPTR, new);
    P_LASTLN++;
    P_CURLN++;
    P_CURPTR = new;
    str = cp + 1;
  }
  while (*cp != EOS);
  return 1;
}


/*  join.c  */
static int join (int first, int last)
{
  char buf[MAXLINE];
  char *cp = buf, *str;
  LINE *lin;
  int num;

  if (first <= 0 || first > last || last > P_LASTLN)
    return (ERR);
  if (first == last) {
    setCurLn (first);
    return 0;
  }
  lin = getptr (first);
  for (num = first; num <= last; num++) {
    str = gettxtl (lin);
    while (*str) {
      if (cp >= buf + MAXLINE - 1) {
        add_message ("line too long\n");
        return (ERR);
      }
      *cp++ = *str++;
    }
    lin = getnextptr (lin);
  }
  *cp = EOS;
  del (first, last);
  if (ins (buf) < 0)
    return MEM_FAIL;
  P_FCHANGED = TRUE;
  return 0;
}


/*  move.c
 *  Unlink the block of lines from P_LINE1 to P_LINE2, and relink them
 *  after line "num".
 */
static int move (int num)
{
  int range;
  LINE *before, *first, *last, *after;

  if (P_LINE1 <= num && num <= P_LINE2)
    return (ERR);
  range = P_LINE2 - P_LINE1 + 1;
  before = getptr (prevln (P_LINE1));
  first = getptr (P_LINE1);
  last = getptr (P_LINE2);
  after = getptr (nextln (P_LINE2));

  relink (before, after, before, after);
  P_LASTLN -= range;            /* per AST's posted patch 2/2/88 */
  if (num > P_LINE1)
    num -= range;

  before = getptr (num);
  after = getptr (nextln (num));
  relink (before, first, last, after);
  relink (last, after, before, first);
  P_LASTLN += range;            /* per AST's posted patch 2/2/88 */
  setCurLn (num + range);
  return (1);
}


static int transfer (int num)
{
  int mid, lin, ntrans;

  if (P_LINE1 <= 0 || P_LINE1 > P_LINE2)
    return (ERR);

  mid = num < P_LINE2 ? num : P_LINE2;

  setCurLn (num);
  ntrans = 0;

  for (lin = P_LINE1; lin <= mid; lin++) {
    if (ins (gettxt (lin)) < 0)
      return MEM_FAIL;
    ntrans++;
  }
  lin += ntrans;
  P_LINE2 += ntrans;

  for (; lin <= P_LINE2; lin += 2) {
    if (ins (gettxt (lin)) < 0)
      return MEM_FAIL;
    P_LINE2++;
  }
  return (1);
}


/*  optpat.c    */
static regexp *optpat (void)
{
  char delim, str[MAXPAT], *cp;

  delim = *inptr++;
  if (delim == NL)
    return P_OLDPAT;
  cp = str;
  while (*inptr != delim && *inptr != NL && *inptr != EOS &&
    cp < str + MAXPAT - 1) {
    if (*inptr == ESCAPE && inptr[1] != NL)
      *cp++ = *inptr++;
    *cp++ = *inptr++;
  }

  *cp = EOS;
  if (*str == EOS)
    return (P_OLDPAT);
  if (P_OLDPAT)
    free (P_OLDPAT);
  return P_OLDPAT = regcomp (str);
}

/* regerror.c */
void regerror (char *s)
{
  add_message ("ed: %s\n", s);
}


static int set (void)
{
  char word[16];
  int i;

  if (*(++inptr) != 't') {
    if (*inptr != SP && *inptr != HT && *inptr != NL)
      return (ERR);
  } else
    inptr++;

  if ((*inptr == NL)) {
    add_message ("ed version %d.%d\n", version / 100, version % 100);
    add_message ("number %s, list %s\n",
      P_NFLG ? "ON" : "OFF", P_LFLG ? "ON" : "OFF");
    return (0);
  }

  Skip_White_Space;
  for (i = 0; *inptr != SP && *inptr != HT && *inptr != NL;)
    word[i++] = *inptr++;
  word[i] = EOS;
#if 0
  for (t = tbl; t->t_str; t++) {
    if (strcmp (word, t->t_str) == 0) {
      *t->t_ptr = t->t_val;
      return (0);
    }
  }
#endif
  return SET_FAIL;
}

#ifndef relink
void relink (a, x, y, b)
LINE *a, *x, *y, *b;
{
  x->l_prev = a;
  y->l_next = b;
}
#endif


static void set_ed_buf (void)
{
  relink (&P_LINE0, &P_LINE0, &P_LINE0, &P_LINE0);
  P_CURLN = P_LASTLN = 0;
  P_CURPTR = &P_LINE0;
}


/*  subst.c */
static int subst (regexp * pat, char *sub, int gflg, int pflag)
{
  int nchngd;
  char *txtptr;
  char *new, buf[MAXLINE];
  int still_running = 1;
  LINE *lastline = getptr (P_LINE2);

  if (P_LINE1 <= 0)
    return (SUB_FAIL);
  nchngd = 0;                   /* reset count of lines changed */

  for (setCurLn (prevln (P_LINE1)); still_running;) {
    nextCurLn ();
    new = buf;
    if (P_CURPTR == lastline)
      still_running = 0;
    if (regexec (pat, txtptr = gettxtl (P_CURPTR))) {
      do {
        /* Copy leading text */
        int diff = pat->startp[0] - txtptr;
        strncpy (new, txtptr, diff);
        new += diff;
        /* Do substitution */
        new = regsub (pat, sub, new);
        txtptr = pat->endp[0];
      }
      while (gflg && !pat->reganch && regexec (pat, txtptr));

      /* Copy trailing chars */
      while (*txtptr) {
        *new = *txtptr;
        new++;
        txtptr++;
      }

      if (new >= buf + MAXLINE)
        return (SUB_FAIL);
      *new = EOS;
      del (P_CURLN, P_CURLN);
      if (ins (buf) < 0)
        return MEM_FAIL;
      nchngd++;
      if (pflag)
        doprnt (P_CURLN, P_CURLN);
    }
  }
  return ((nchngd == 0 && !gflg) ? SUB_FAIL : nchngd);
}

/*  docmd.c
 *  Perform the command specified in the input buffer, as pointed to
 *  by inptr.  Actually, this finds the command letter first.
 */
static int docmd (int glob)
{
  static char rhs[MAXPAT];
  regexp *subpat;
  int c, err, line3;
  int apflg, pflag, gflag;
  int nchng;
  char *fptr;

  pflag = FALSE;
  Skip_White_Space;

  c = *inptr++;
  switch (c) {
  case NL:
    if (P_NLINES == 0 && (P_LINE2 = nextln (P_CURLN)) == 0)
      return (ERR);
    setCurLn (P_LINE2);
    return (1);

  case '=':
    add_message ("%d\n", P_LINE2);
    break;

  case 'a':
    if (*inptr != NL || P_NLINES > 1)
      return (ERR);

    if (append (P_LINE1, glob) < 0)
      return (ERR);
    P_FCHANGED = TRUE;
    break;

  case 'c':
    if (*inptr != NL)
      return (ERR);

    if (deflt (P_CURLN, P_CURLN) < 0)
      return (ERR);

    if (del (P_LINE1, P_LINE2) < 0)
      return (ERR);
    if (append (P_CURLN, glob) < 0)
      return (ERR);
    P_FCHANGED = TRUE;
    break;

  case 'd':
    if (*inptr != NL)
      return (ERR);

    if (deflt (P_CURLN, P_CURLN) < 0)
      return (ERR);

    if (del (P_LINE1, P_LINE2) < 0)
      return (ERR);
    if (nextln (P_CURLN) != 0)
      nextCurLn ();
    P_FCHANGED = TRUE;
    break;

  case 'e':
    if (P_NLINES > 0)
      return (ERR);
    if (P_FCHANGED)
      return CHANGED;
    /*FALL THROUGH */
  case 'E':
    if (P_NLINES > 0)
      return (ERR);

    if (*inptr != ' ' && *inptr != HT && *inptr != NL)
      return (ERR);

    if ((fptr = getfn (1)) == NULL)
      return (ERR);

    clrbuf ();
    (void) doread (0, fptr);

    strcpy (P_FNAME, fptr);
    P_FCHANGED = FALSE;
    break;

  case 'f':
    if (P_NLINES > 0)
      return (ERR);

    if (*inptr != ' ' && *inptr != HT && *inptr != NL)
      return (ERR);

    if ((fptr = getfn (1)) == NULL)
      return (ERR);

    if (P_NOFNAME)
      add_message ("%s\n", P_FNAME);
    else
      strcpy (P_FNAME, fptr);
    break;

  case 'i':
    if (*inptr != NL || P_NLINES > 1)
      return (ERR);

    if (append (prevln (P_LINE1), glob) < 0)
      return (ERR);
    P_FCHANGED = TRUE;
    break;

  case 'j':
    if (*inptr != NL || deflt (P_CURLN, P_CURLN + 1) < 0)
      return (ERR);

    if (join (P_LINE1, P_LINE2) < 0)
      return (ERR);
    break;

  case 'k':
    Skip_White_Space;

    if (*inptr < 'a' || *inptr > 'z')
      return ERR;
    c = *inptr++;

    if (*inptr != ' ' && *inptr != HT && *inptr != NL)
      return (ERR);

    P_MARK[c - 'a'] = P_LINE1;
    break;

  case 'l':
    if (*inptr != NL)
      return (ERR);
    if (deflt (P_CURLN, P_CURLN) < 0)
      return (ERR);
    if (dolst (P_LINE1, P_LINE2) < 0)
      return (ERR);
    break;

  case 'm':
    if ((line3 = getone ()) < 0)
      return (ERR);
    if (deflt (P_CURLN, P_CURLN) < 0)
      return (ERR);
    if (move (line3) < 0)
      return (ERR);
    P_FCHANGED = TRUE;
    break;

  case 'P':
  case 'p':
    if (*inptr != NL)
      return (ERR);
    if (deflt (P_CURLN, P_CURLN) < 0)
      return (ERR);
    if (doprnt (P_LINE1, P_LINE2) < 0)
      return (ERR);
    break;

  case 'q':
    if (P_FCHANGED)
      return CHANGED;
    /*FALL THROUGH */
  case 'Q':
    clrbuf ();
    if (*inptr == NL && P_NLINES == 0 && !glob)
      return (EOF);
    else
      return (ERR);

  case 'r':
    if (P_NLINES > 1)
      return (ERR);

    if (P_NLINES == 0)          /* The original code tested */
      P_LINE2 = P_LASTLN;       /*  if(P_NLINES = 0)        */
    /* which looks wrong.  RAM  */

    if (*inptr != ' ' && *inptr != HT && *inptr != NL)
      return (ERR);

    if ((fptr = getfn (0)) == NULL)
      return (ERR);

    if ((err = doread (P_LINE2, fptr)) < 0)
      return (err);
    P_FCHANGED = TRUE;
    break;

  case 's':
    if (*inptr == 'e')
      return (set ());
    Skip_White_Space;
    if ((subpat = optpat ()) == NULL)
      return (ERR);
    if ((gflag = getrhs (rhs)) < 0)
      return (ERR);
    if (*inptr == 'p')
      pflag++;
    if (deflt (P_CURLN, P_CURLN) < 0)
      return (ERR);
    if ((nchng = subst (subpat, rhs, gflag, pflag)) < 0)
      return (ERR);
    if (nchng)
      P_FCHANGED = TRUE;
    break;

  case 't':
    if ((line3 = getone ()) < 0)
      return (ERR);
    if (deflt (P_CURLN, P_CURLN) < 0)
      return (ERR);
    if (transfer (line3) < 0)
      return (ERR);
    P_FCHANGED = TRUE;
    break;

  case 'W':
  case 'w':
    apflg = (c == 'W');

    if (*inptr != ' ' && *inptr != HT && *inptr != NL)
      return (ERR);

    if ((fptr = getfn (1)) == NULL)
      return (ERR);

    if (deflt (1, P_LASTLN) < 0)
      return (ERR);
    if (dowrite (P_LINE1, P_LINE2, fptr, apflg) < 0)
      return (ERR);
    P_FCHANGED = FALSE;
    break;

  case 'x':
    if (*inptr == NL && P_NLINES == 0 && !glob) {
      if ((fptr = getfn (1)) == NULL)
        return (ERR);
      if (dowrite (1, P_LASTLN, fptr, 0) >= 0)
        return (EOF);
    }
    return (ERR);

  case 'z':
    if (deflt (P_CURLN, P_CURLN) < 0)
      return (ERR);

    switch (*inptr) {
    case '-':
      if (doprnt (P_LINE1 - 21, P_LINE1) < 0)
        return (ERR);
      break;

    case '.':
      if (doprnt (P_LINE1 - 11, P_LINE1 + 10) < 0)
        return (ERR);
      break;

    case '+':
    case '\n':
      if (doprnt (P_LINE1, P_LINE1 + 21) < 0)
        return (ERR);
      break;
    }
    break;

  default:
    return (ERR);
  }
  return (0);
}                               /* docmd */


/*  doglob.c    */
static int doglob (void)
{
  int lin, stat;
  char *cmd;
  LINE *ptr;

  cmd = inptr;

  for (;;) {
    ptr = getptr (1);
    for (lin = 1; lin <= P_LASTLN; lin++) {
      if (ptr->l_stat & LGLOB)
        break;
      ptr = getnextptr (ptr);
    }
    if (lin > P_LASTLN)
      break;

    ptr->l_stat &= ~LGLOB;
    P_CURLN = lin;
    P_CURPTR = ptr;
    inptr = cmd;
    if ((stat = getlst ()) < 0)
      return (stat);
    if ((stat = docmd (1)) < 0)
      return (stat);
  }
  return (P_CURLN);
}                               /* doglob */


void ed_start (char *file_arg)
{
  if (command_giver->ed_buffer)
    error ("Tried to start an ed session, when already active.\n");
  if (command_giver != current_object)
    error ("Illegal start of ed.\n");
  command_giver->ed_buffer =
    (struct ed_buffer *) xalloc (sizeof (struct ed_buffer));
  memset ((char *) command_giver->ed_buffer, '\0', sizeof (struct ed_buffer));
  command_giver->ed_buffer->truncflg = 1;
  command_giver->ed_buffer->eightbit = 1;
  command_giver->ed_buffer->CurPtr = &command_giver->ed_buffer->Line0;
  set_ed_buf ();

  if (file_arg && doread (0, file_arg) == 0) {
    setCurLn (1);
    strcpy (P_FNAME, file_arg);
    set_prompt (":");
    return;
  }
  set_prompt (":");
}

void ed_cmd (char *str)
{
  int stat =0;

  if (P_APPENDING) {
    more_append (str);
    return;
  }
  strcat (str, "\n");

  strcpy (inlin, str);
  inptr = inlin;
  if (getlst () >= 0)
    if ((stat = ckglob ()) != 0) {
      if (stat >= 0 && (stat = doglob ()) >= 0) {
        setCurLn (stat);
        return;
      }
    } else {
      if ((stat = docmd (0)) >= 0) {
        if (stat == 1)
          doprnt (P_CURLN, P_CURLN);
        return;
      }
    }
  switch (stat) {
  case EOF:
    free (command_giver->ed_buffer);
    command_giver->ed_buffer = 0;
    add_message ("Exit from ed.\n");
    set_prompt ("> ");
    return;
  case FATAL:
    free (command_giver->ed_buffer);
    command_giver->ed_buffer = 0;
    add_message ("FATAL ERROR\n");
    set_prompt ("> ");
    return;
  case CHANGED:
    add_message ("File has been changed.\n");
    break;
  case SET_FAIL:
    add_message ("`set' command failed.\n");
    break;
  case SUB_FAIL:
    add_message ("string substitution failed.\n");
    break;
  case MEM_FAIL:
    add_message ("Out of memory: text may have been lost.\n");
    break;
  default:
    add_message ("Unrecognized or failed command.\n");
    /*  Unrecognized or failed command (this  */
    /*  is SOOOO much better than "?" :-)     */
  }
}