/* This file is http://ecce.sourceforge.net/ecce.c */
/* It is written in reasonably portable C and should */
/* be easy to compile with any C compiler, eg on Linux:
   cc -o ecce ecce.c
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <errno.h>

/**************************************************/
/*                                                */
/*                                                */
/*                     E C C E                    */
/*                                                */
/*                                                */
/*     ECCE was designed by Hamish Dewar, now     */
/* retired.  This implementation is by Graham     */
/* Toal, of the Edinburgh Computer History        */
/* Project.                                       */
/*                                                */
/* This source is released into the public domain */
/* by the author, without restriction.            */
/*                                                */
/* (c) Graham Toal, 1984. (Original BCPL version, */
/*   translated into C in 1992).                  */
/**************************************************/

/**************************************************************************/

#define NOTE_FILE "/tmp/Note0"
              /* Name of temp file for multiple contexts - system dependant. */
              /* Something like "/tmp/Note%c" would be a small improvement,  */
              /* but using a proper function like tmpnam() would be best.    */

              /* Unfortunately tmpnam is deprecated due to timing issues     */
	      /* with setting up file permissions - but it is the only call  */
              /* in this area that is portable to Win/DOS, and I'm trying    */
              /* to keep this source OS-independent. (without ifdef's)       */

              /* This is the remaining code issue I'ld like to fix before    */
              /* moving this to sourceforge.                                 */


#define CONTEXT_OFFSET (strlen(NOTE_FILE)-1)
              /* Index of variable part in name above (i.e. of '0')         */

static char *ProgName = NULL;
static char *parameter[4] = {NULL, NULL, NULL, NULL}; /* parameters - from, to, log, command */
static char *commandp = NULL;

#define    F       0  /* FROM */
#define    T       1  /* TO */
#define    L       2  /* LOG */
#define    C       3  /* COMMAND */

unsigned long estimate_buffer_size(char *fname)
{
  FILE *tmp = fopen(fname, "rw");
  unsigned long maxbuf = 0UL;
  long rc;

  /* since we allocate RAM for the whole file, don't bother handling
     files longer than 32 bits.  It's just a text editor after all... */

  if (tmp == NULL) return 2UL*1024UL*1024UL;
  (void)fseek(tmp, 0L, SEEK_END);
  rc = ftell(tmp);
  if ((rc < 0) || ferror(tmp)) maxbuf = 0UL; else maxbuf = (unsigned long)rc;
  (void)fclose(tmp);
  return (maxbuf + 1024UL*256UL) * 3UL;
}

/**************************************************************************/

#define FALSE (0!=0)
#define TRUE (0==0)

/* Types */

typedef int bool;
typedef char *cindex;

/* Consts */

#define    bs              8
#define    bell            7
#define    nul             0
#define    del             127
/* The casebit logic only works on 8-bit characters.  Will need to
   rewrite case handling if/when we move to UTF 32-bit encoding */
#define    casebit         ('a'-'A')
#define    minusbit        casebit
#define    plusbit         0x80
#define    Max_command_units 127
#define    Max_parameter     127
#define    Max_prompt_length 127
#define    rep             1
#define    txt             2
#define    scope           4
#define    sign            8
#define    delim           16
#define    numb            32
#define    ext             64
#define    err             128
#define    dig             0
#define    pc              1
#define    lpar            2
#define    comma           3
#define    rpar            4
#define    plus            5
#define    minus           6
#define    pling           7
#define    star            8
#define    termin          15

void init_globals (void); 
void free_buffers (void); 
void local_echo (int *sym);        /* Later, make this a char fn. */
void read_sym (void); 
bool fail_with (char *mess, char culprit); 
void percent (char Command_sym); 
void unchain(void); 
void stack(void); 
void execute_command(void); 
void Scan_sign(void);                        /* Could be a macro */
void Scan_scope(void);                       /* ditto macro */
void Scan_text(void); 
void Scan_repeat (void); 
bool analyse (void); 
void load_file (void); 
bool execute_unit (void); 
void execute_all (void); 
char case_op (char sym);                /* should be made a macro */
bool right (void); 
bool left (void); 
void right_star(void);                       /* Another macro */
void left_star(void);                        /* Likewise... */
void move (void); 
void move_back(void); 
void move_star (void); 
void move_back_star (void); 
void insert (void); 
void insert_back (void); 
bool verify(void); 
bool verify_back (void); 
bool find (void); 
bool find_back (void);

/* Global variables */

static unsigned long buffer_size = 0UL;
static char *note_file;
static bool  ok;
static bool  printed;
static long  stopper;
static int   max_unit;
static char  pending_sym;

/* significance of file pointers using the 'buffer gap' method: */

/* [NL] o n e NL t w . . . o NL n e x t NL . . NL l a s t NL [NL] */
/*      !        !   !     !  !                                !  */
/*      f        l   p     f  l                                f  */
/*      b        b   p     p  e                                e  */
/*      e        e            n                                n  */
/*      g        g            d                                d  */

/* Note that when the buffer is 100% full, pp and fp are equal,
   and any insertion operations will fail.  This is valid as
   pp is exclusive and fp is inclusive. */

/* When editing a secondary input buffer, these pointers are saved
   and re-created within the buffer gap */

/* Hamish's implementations forced the top part of the buffer out
   to file when the buffer was full (cf 'makespace()'); this isn't
   really an option in today's environment.  Alternative choices
   are:

   1) crash.  (what we did, prior to 2.7)
   2) fail to insert (what we do now)
   3) expand the buffer (realloc, or malloc+free)
      - I don't like this because at some point you do run out
        of RAM or VM, and have to fail anyway.  Since the most
        likely reason this is happening is a bad user command
        (eg (b0)0 ) rather than a file that is genuinely too large,
        I'd prefer to fail on the first instance of it going wrong.
   4) use memory-mapped files (unix has them now too, very similar
        to what we had on EMAS) - but the argument against is just
        a delayed version of (3) above.

   Note that the failure mode of this code is *not* atomic.
   A complete 'get line' or 'insert string' operation would fail
   in Hamish's implementation.  Here it fails on the individual
   character level.  I chose this model primarily to lower the
   cost of the buffer-full test.
 */

static cindex fbeg;
static cindex lbeg;
static cindex pp;
static cindex fp;
static cindex lend;
static cindex fend;

static int   type;
static char  command;
static long  repeat_count;
static long  limit;
static int   pointer;
static int   last_unit;
static int   this_unit;
static int   pos;
static int   endpos;
static int   sym;        /************* sym has to be an int as
                                        it is tested against EOF ************/
static long  number;
static cindex pp_before;
static cindex fp_before;
static cindex ms;
static cindex ms_back;
static cindex ml;
static cindex ml_back;
static int   to_upper_case;
static int   to_lower_case;
static int   caseflip;
static bool  blank_line;
static char *eprompt;
static cindex noted;
static int   changes;
static bool  in_second;
static char *com_prompt;

static int sym_type[256] = {
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   ext+termin,          /*NL*/
   err,                 /* */
   ext+numb+7,          /*!*/
   delim,               /*"*/
   err,                 /*#*/
   err,                 /*$*/
   ext+1,               /*%*/
   err,                 /*&*/
   delim,               /*'*/
   ext+2,               /*(*/
   ext+4,               /*)*/
   ext+numb+8,          /***/
   ext+5,               /*+*/
   ext+3,               /*,*/
   ext+6,               /*-*/
   delim,               /*.*/
   delim,               /*slash*/
   ext+numb+0,          /*0*/
   ext+numb+0,          /*1*/
   ext+numb+0,          /*2*/
   ext+numb+0,          /*3*/
   ext+numb+0,          /*4*/
   ext+numb+0,          /*5*/
   ext+numb+0,          /*6*/
   ext+numb+0,          /*7*/
   ext+numb+0,          /*8*/
   ext+numb+0,          /*9*/
   delim,               /*:*/
   ext+15,              /*;*/
   ext+2,               /*<*/
   delim,               /*=*/
   ext+4,               /*>*/
   0,                   /*?*/
   err,                 /*@*/
   scope,               /*A*/
   sign+rep,            /*B*/
   sign+rep,            /*C*/
   sign+scope+txt+rep,  /*D*/
   sign+rep,            /*E*/
   sign+scope+txt+rep,  /*F*/
   sign+rep,            /*G*/
   scope,               /*H*/
   sign+txt+rep,        /*I*/
   sign+rep,            /*J*/
   sign+rep,            /*K*/
   sign+rep,            /*L*/
   sign+rep,            /*M*/
   0,                   /*N*/
   err,                 /*O*/
   sign+rep,            /*P*/
   err,                 /*Q*/
   sign+rep,            /*R*/
   sign+txt,            /*S*/
   sign+scope+txt+rep,  /*T*/
   sign+scope+txt+rep,  /*U*/
   sign+txt,            /*V*/
   err,                 /*W*/
   err,                 /*X*/
   err,                 /*Y*/
   err,                 /*Z*/
   ext+2,               /*[*/
   0,                   /*\*/
   ext+4,               /*]*/
   ext+6,               /*^*/
   delim,               /*_*/
   err,                 /*@*/
   err,                 /*A*/
   sign+rep,            /*B*/
   sign+rep,            /*C*/
   sign+scope+txt+rep,  /*D*/
   sign+rep,            /*E*/
   sign+scope+txt+rep,  /*F*/
   sign+rep,            /*G*/
   err,                 /*H*/
   sign+txt+rep,        /*I*/
   sign+rep,            /*J*/
   sign+rep,            /*K*/
   sign+rep,            /*L*/
   sign+rep,            /*M*/
   err,                 /*N*/
   err,                 /*O*/
   sign+rep,            /*P*/
   err,                 /*Q*/
   sign+rep,            /*R*/
   sign+txt,            /*S*/
   sign+scope+txt+rep,  /*T*/
   sign+scope+txt+rep,  /*U*/
   sign+txt,            /*V*/
   err,                 /*W*/
   err,                 /*X*/
   err,                 /*Y*/
   err,                 /*Z*/
   ext+2,               /*[*/
   0,                   /*\*/
   ext+4,               /*]*/
   ext+6,               /*^*/
   delim                /*_*/
/* May change some of these to delim at users discretion */
 , err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err, 
   err, err, err, err, err, err, err, err
};

static cindex a;
static FILE *main_in;
static FILE *main_out;
static FILE *tty_in;
static FILE *tty_out;
static FILE *log_out;

static char *com;
static int  *link;
static char *text;
static long *num;
static long *lim;

/*****************************************************************************/

static int IntSeen = FALSE; /* set asynchronously by signal routine on ^C */

void gotint(int n) {
  n = n; /* Supress the annoying 'not used' warning message... */
  IntSeen = TRUE;
}

char *backup_save;

int main(int argc, char **argv) {
  static char backup_save_buf[256+L_tmpnam+1];
                               /* L_tmpnam on Win/DOS (LCC32) doesn't include path */
  int argno = 1, inoutlog = 0;
  char *s;

  backup_save = tmpnam(backup_save_buf);

  ProgName = argv[0];
  s = strrchr(ProgName, '/');
  if (s == NULL) s = strrchr(ProgName, '\\');
  if (s == NULL) s = strrchr(ProgName, ':');
  if (s == NULL) s = strrchr(ProgName, ']');
  if (s == NULL) s = ProgName; else s = s+1;
  ProgName = strdup(s);
  s = strchr(ProgName, '.'); if (s != NULL) *s = '\0';

  /* decode argv into parameter[0..3] and buffer_size */
  for (;;) {
    if (argno == argc) break;
    if ((argv[argno][0] == '-') && (argv[argno][1] != '\0')) {
      int offset = 1;
      if (argv[argno][1] == '-') offset += 1;
      if (strcmp(argv[argno]+offset, "from") == 0) {
        parameter[F] = argv[argno+1];
      } else if (strcmp(argv[argno]+offset, "to") == 0) {
        parameter[T] = argv[argno+1];
      } else if (strcmp(argv[argno]+offset, "log") == 0) {
        parameter[L] = argv[argno+1];
      } else if (strcmp(argv[argno]+offset, "command") == 0) {
        parameter[C] = argv[argno+1]; commandp = parameter[C];
      } else if (strcmp(argv[argno]+offset, "size") == 0) {
        char *buf_size_str, *endptr;
        buf_size_str = argv[argno+1];
        errno = 0;
        buffer_size = strtoul(buf_size_str, &endptr, 10);
        if (errno != 0) {
          fprintf(stderr, "%s: bad size parameter '%s'\n", ProgName, buf_size_str);
          exit(1);
	}
        if (*endptr != '\0') {
          if (strcasecmp(endptr, "k") == 0) {
            buffer_size *= 1024UL;
	  } else if (strcasecmp(endptr, "m") == 0) {
            buffer_size *= (1024UL*1024UL);
	  } else {
            fprintf(stderr,
                    "%s: bad unit type '%s' (expected %luK or %luM)\n",
                    ProgName, endptr, buffer_size, buffer_size);
            exit(1);
	  }
	}
      } else {
        fprintf (stderr,
                 "%s: unknown option '%s'\n",
		 ProgName, argv[argno]);
        exit(1);
      }       
      if (argv[argno+1] == NULL) argno += 1; else argno += 2;
    } else {
      /* positional parameters */
      parameter[inoutlog++] = argv[argno++];
    }
  }

  if (buffer_size == 0UL) buffer_size = estimate_buffer_size(parameter[F]);
   parameter[F] = argv[1];

   if (parameter[F] == NULL) {
      fprintf (stderr,
         "%s: {-from} infile {{-to} outfile}? {-log file}? {-command 'commands;%%c'} {-size bytes}?\n",
          ProgName);
      exit (30);
   }

   IntSeen = FALSE;

   tty_in = stdin;
   tty_out = stderr;

   if ((strcmp(parameter[F], "-") == 0) || (strcmp(parameter[F], "/dev/stdin") == 0)) {
      /* If the input file is stdin, you cannot read commands from stdin as well. */
      if (commandp == NULL) {
        fprintf(stderr, "%s: \"-command '...'\" option required when input file is standard input\n", ProgName); exit(1);
      } 
      main_in = stdin;
      /* What follows is a dirty hack to allow ecce to be used interactively as part of a pipe */
      /* I'm not at all sure this should even be supported */
      tty_in = fopen("/dev/tty", "rb");
      if (tty_in) {
	fprintf(stderr, "%s: using /dev/tty for command input\n", ProgName);
      } else {
            tty_in = fopen("CON:", "r");
            if (tty_in) { 
	       fprintf(stderr, "%s: using CON: for command input\n", ProgName);
	    } else {
               tty_in = fopen("/dev/null", "rb");
               if (tty_in == NULL) tty_in = fopen("NUL:", "rb");
	       fprintf(stderr, "%s: warning - no command input stream\n", ProgName);
               if (tty_in == NULL) tty_in = stdin; /* It'll be EOF by the time it is used */
	    }
      }
   } else {
      main_in = fopen (parameter[F], "rb");
   }

   if (main_in == NULL) {
      fprintf (stderr, "File \"%s\" not found\n", parameter[F]);
      exit (30);
   }

   if (parameter[L] == NULL) {
      log_out = NULL;
   } else {
      log_out = fopen (parameter[L], "wb");
      if (log_out == NULL) {
         fprintf (stderr, "%s: Warning - can't create \"%s\"\n",
          ProgName, parameter[L]);
      }
   }

   init_globals ();

   a[0]           = '\n';
   a[buffer_size] = '\n';

   fprintf (tty_out, "Ecce\n");

   if (main_in != NULL) load_file ();

   signal(SIGINT, &gotint);

   percent ('E'); /* Select either-case searches, case-flipping C command. */
   for (;;) {
      if (analyse ()) {
         printed = FALSE;
         execute_all ();
         command = 'P';
         repeat_count = 1L;
         if (!printed) execute_command ();
      }

      if (IntSeen) {
        signal(SIGINT, &gotint);

        IntSeen = FALSE;
        fprintf(stderr, "* Escape!\n");
      }

   }
}

void init_globals (void) {

   a = malloc (buffer_size+1);

   note_file = malloc (Max_parameter+1);

   com  = malloc (Max_command_units+1); /* char */
   link = (int *) malloc ((Max_command_units+1)*sizeof(int));
   text = (char *) malloc (Max_command_units+1);

   num = (long *) malloc ((Max_command_units+1)*sizeof(long));
   lim = (long *) malloc ((Max_command_units+1)*sizeof(long));

   com_prompt = malloc (Max_prompt_length+1);

   if (a == NULL || note_file == NULL || com == NULL ||
    link == NULL || text == NULL || num == NULL || lim == NULL ||
    com_prompt == NULL) {
      fprintf (stderr, "Unable to claim buffer space\n");
      free_buffers();
      exit (40);
   }

   fprintf (stderr, "Buffer space = %d KBytes\n", (int)(buffer_size>>10));


   fbeg = a+1;
   lbeg = fbeg;
   pp = lbeg;
   fp = a+buffer_size;
   lend = fp;
   fend = lend;
   ms = NULL;
   ms_back = NULL;
   stopper = 0 - buffer_size;
   max_unit = -1;
   pending_sym = '\n';
   blank_line = TRUE;

   (void)strcpy (note_file, NOTE_FILE);
   noted = NULL;
   changes = 0;
   in_second = FALSE;
   (void)strcpy (com_prompt, ">");
}

void free_buffers (void) { /* only needed if checking that we have no heap lossage at end */
  if (a) free (a); a = NULL;
  if (lim) free (lim); lim = NULL;
  if (num) free (num); num = NULL;
  if (text) free (text); text = NULL;
  if (link) free (link); link = NULL;
  if (com) free (com); com = NULL;
  if (com_prompt) free (com_prompt); com_prompt = NULL;
  if (note_file) free (note_file); note_file = NULL;
  if (ProgName) free (ProgName); ProgName = NULL;
}

void local_echo (int *sym) {       /* Later, make this a char fn. */
   int lsym;

   if (commandp) {
      lsym = *commandp;
      if (lsym == '\0') {lsym = '\n'; commandp = NULL;} else commandp += 1;
      blank_line = (lsym == '\n');
      *sym = lsym;
      return;
   }

   if (blank_line) {fprintf(tty_out, eprompt); fflush(tty_out); }    /* stderr usually unbuffered, but flush needed for cygwin */

   lsym = fgetc (tty_in);
   if (IntSeen) {
     /* Tuned for windows */
     IntSeen = FALSE;
     signal(SIGINT, &gotint);
     lsym = '\n';
     fputc('^', tty_out); fputc('C', tty_out); fputc('\n', tty_out);
   }
   
   if (lsym == EOF) {

      IntSeen = FALSE;
      signal(SIGINT, SIG_IGN);
      fputc('\n', tty_out); /* Undo the prompt */

      percent ('c');
      exit (50);
   }

   if (log_out != NULL) {
      fputc (lsym, log_out);
   }
   blank_line = (lsym == '\n');
   *sym = lsym;
}

void read_sym (void) {
   if (pending_sym == 0) {
      do { local_echo (&sym); } while (sym == ' ');
                               /* Better test wanted for noise */
   } else {
      sym = pending_sym;   /* C has an ungetc() but not very standard... */
      pending_sym = 0;
   }
}

bool fail_with (char *mess, char culprit) {
 int dirn_sign;

   if (('a' <= culprit) && (culprit <= 'z')) {
      dirn_sign = '-';
   } else {
     if ((culprit & plusbit) != 0) {
        dirn_sign = '+';
     } else {
        dirn_sign = ' ';
     }
   }
   culprit = culprit & (~plusbit);
   if (('A' <= culprit) && (culprit <= 'Z'))
      culprit = culprit | casebit;
   fprintf (stderr, "* %s %c%c\n", mess, culprit, dirn_sign);
   do { read_sym (); } while (sym_type[sym] != sym_type[';']);
   return (ok = FALSE);
}


void read_item(void) {
   int saved_digit;
   read_sym ();
   if (('a' <= sym) && (sym <= 'z'))
      sym = sym - casebit;
   type = sym_type[sym];
   if ((type & ext) == 0) return;

   switch (type & 15) {

      case star:
         number = 0L;
         return;

      case pling:
         number = stopper-1;
         return;

      case dig:
         saved_digit = sym;
         number = 0L;
         do {
            number = (number * 10) + (sym - '0');
            read_sym();
         } while (('0' <= sym) && (sym <= '9'));
         pending_sym = sym;
         sym = saved_digit; /* for printing in errors */
         return;

      default:
         return;
   }
}

void percent (char Command_sym) {
   static char note_sec = '0'; /* This one MUST be a static */
   cindex P;
   int inoutlog;
   int sec_no; /************ sec_no is also an int for now -
                                because local_echo(&sym) expects it to be! */
   bool file_wanted; /* %s2 or %s2=fred ? */
   char sec_file[256], *sec_filep;
   ok = TRUE;
   if (!(('a' <= (Command_sym | casebit)) && ((Command_sym | casebit) <= 'z'))) {
      (void) fail_with ("letter for", '%');
      return;
   }
   switch (Command_sym) {

      case 'L':
         to_upper_case = ~0;
         to_lower_case = casebit;
/*         to_lower_case = 0; ---- standard ecce */
         caseflip = 0;
         break;

      case 'U':
         to_upper_case = ~casebit;
         to_lower_case = 0;
/*         to_lower_case = casebit; ---- standard ecce */
         caseflip = 0;
         break;

      case 'N':
         to_upper_case = ~0;
         to_lower_case = 0;
         caseflip = casebit;
         break;

      case 'E':
         to_upper_case = ~casebit; /* Only for searches - not in C command */
         to_lower_case = 0;
         caseflip = casebit;
         break;

      case 'V':
         fprintf (tty_out, "Ecce V2.9a in C Sun Jan 20 12:48:30 CDT 2013\n");
         break;

      case 'W':
	if ((strcmp(parameter[parameter[T] == NULL ? F : T], "-") == 0) ||
            ((parameter[T] != NULL) && (strcmp(parameter[T], "/dev/stdout") == 0))) {
           fprintf(stderr, "* %W is not allowed when the output file is stdout\n");
	   break;
	 }
      case 'C':
         do { read_sym (); } while (sym_type[sym] != sym_type[';']);
   
      case 'c':

         if (parameter[T] == NULL) {
            inoutlog = F;         /* So use input file as output file */
         } else {
            inoutlog = T;
         }

         if (in_second) { /* Copied bit */
         /*************** This block is copied from the %S code below;
           it ensures that the main edit buffer is pulled in when closing
           the edit and writing out the file.  This is a quick hack: I
           should change this and the copy in percent('S') so that both
           share the same subroutine ensure_main_edit() *****************/
            FILE *sec_out = fopen (note_file, "wb");
            (void)strcpy (com_prompt, ">");
            if (sec_out == NULL) {
               (void) fail_with ("Cannot save context", ' ');
               break;
            }
            P = fbeg;
            for (;;) {
               if (P == pp) P = fp;
               if (P == fend) break;
               fputc (*P++, sec_out);
            }
            fclose (sec_out);
            pp = fbeg - 1;
            fp = fend + 1;
            fbeg = a+1;
            fend = a+buffer_size;
            lbeg = pp;
            do { --lbeg; } while (*lbeg != '\n');
            lbeg++;
            lend = fp;
            while (*lend != '\n') lend++;
            in_second = FALSE;
/*
            if (sec_no == 0) {
               / * do nothing. Else note it and re-select it if this is
                  a percent('W') ! * /
            }
 */
         }  /* End of copied bit */
         if (Command_sym == 'c') {
            parameter[inoutlog] = backup_save;
            main_out = fopen (parameter[inoutlog], "wb");
            if (main_out == NULL) {
               fprintf(stderr,
                       "Sorry - I can't save your edit (even %s failed)\n", backup_save);
               exit(90);
            }
            fprintf (tty_out, "Ecce abandoned: saving to %s\n", parameter[inoutlog]);
         } else {
	    if ((strcmp(parameter[inoutlog], "-") == 0) || (strcmp(parameter[inoutlog], "/dev/stdout") == 0))
               main_out = stdout;
            else
               main_out = fopen (parameter[inoutlog], "wb");
            if (main_out == NULL) {
               fprintf (stderr,
                        "Can't create \"%s\" - attempting to save to %s instead\n",
                        parameter[inoutlog], backup_save);
               main_out = fopen (backup_save, "w");
               if (main_out == NULL) {
                 fprintf(stderr, "Cannot save file at all.  Giving up.  Sorry!\n");
                 exit(1);
	       }
            } else {
               if (inoutlog == T) {
                  fprintf (tty_out,
                           "Ecce %s to %s completing.\n", parameter[F], parameter[T]);
               } else {
                  fprintf (tty_out, "Ecce %s completing.\n", parameter[F]);
               }
            }
         }

         P = fbeg;
         for (;;) {
            if (P == pp) P = fp;
            if (P == fend) break;
            fputc (*P++, main_out);
         }
         if (main_out != stdout) fclose (main_out);

         if (Command_sym == 'W') {
            pending_sym = '\n';
            break;
         }

         if (log_out != NULL) {
            fclose (log_out);
         }
/*         fprintf (tty_out, "Ecce complete\n");      */
         free_buffers ();
         exit (0);

      case 'A':
         if (log_out != NULL) {
            fclose (log_out);
         }
         fprintf (stderr, "\nAborted!\n");
         free_buffers ();
         exit (60);

      case 'S':
         local_echo (&sec_no);
         file_wanted = FALSE;
         if (sym_type[sec_no] == sym_type[';']) {sec_no = 0;}
           /* '\0' means main, '0' means 0,
              so a plain '%s' in secondary input means switch back to
              main and in main means switch to 0. */
         else if (sec_no == '=') {sec_no = '0'; file_wanted = TRUE;}
           /* Here '0' is explicit because we never want to switch to
              main with a '%s=fred' call. */
         else  {
            if (sec_no == '!') {sec_no = '?';}
            else if (sec_no == '=') {sec_no = '0'; file_wanted = TRUE;}
            else if (!(('0' <= sec_no) && (sec_no <= '9'))) {
               (void) fail_with ("%S", sec_no);
               return;
            }
            local_echo (&sym);
            if (sym == '=') {
               file_wanted = TRUE;
            } else if (sym_type[sym] != sym_type[';']) {
               (void) fail_with ("%S?", sym);
               return;
            }
         }
         if (file_wanted) {
           sec_filep = &sec_file[0];
           do {
             read_sym();
             *sec_filep++ = sym;
           } while (sym != '\n');
           *--sec_filep = '\0';
         }
         pending_sym = '\n';
         note_file[CONTEXT_OFFSET] = note_sec;
         if (in_second) {
            FILE *sec_out = fopen (note_file, "wb");
            (void)strcpy (com_prompt, ">");
            if (sec_out == NULL) {
               (void) fail_with ("Cannot save context", ' ');
               return;
            }
            P = fbeg;
            for (;;) {
               if (P == pp) P = fp;
               if (P == fend) break;
               fputc (*P++, sec_out);
            }
            fclose (sec_out);
            pp = fbeg - 1;
            fp = fend + 1;
            fbeg = a+1;
            fend = a+buffer_size;
            lbeg = pp;
            do { --lbeg; } while (*lbeg != '\n');
            lbeg++;
            lend = fp;
            while (*lend != '\n') lend++;
            in_second = FALSE;
            if (sec_no == 0) {
               return;
            }
         }
         if (sec_no == 0) sec_no = '0';
         note_file[CONTEXT_OFFSET] = sec_no;
         note_sec = sec_no;
         {
            FILE *sec_in = (file_wanted
                             ? fopen (sec_file, "rb")
                             : fopen (note_file, "rb"));
            if (sec_in == NULL) {
               if (file_wanted) {
                  (void) fail_with ("Cannot open file", ' ');
               } else {
                  (void) fail_with ("Unknown context", sec_no);
               }
               return;
            }
            (void)strcpy (com_prompt, "X>");
            com_prompt[0] = sec_no;
            in_second = TRUE;
            *pp = '\n';

            fbeg = pp + 1;
            fend = fp - 1;
            pp = fbeg;
            fp = fend;
            *fend = '\n';
            lbeg = pp;
            P = pp;
            for (;;) {
               sym = fgetc(sec_in);
               if (sym == EOF) break;
               *P++ = sym;
               if (P == fend) {
                  (void) fail_with ("%S corrupt - no room", ' ');
                  fclose (sec_in);
                  return;
               }
            }
            fclose (sec_in);
            while (P != pp) *--fp = *--P;
            lend = fp;
            while (*lend != '\n') lend++;
         }
         break;

      default:
         (void) fail_with ("Percent", Command_sym);
   }
   do { read_sym(); } while (sym_type[sym] != sym_type[';']);
}

void unchain(void) {
   do {
      pointer = last_unit;
      if (pointer < 0) return;
      last_unit = link[pointer];
      link[pointer] = this_unit;
   } while (com[pointer] != '(');
}

void stack(void) {
   com[this_unit]  = command;
   link[this_unit] = pointer;
   num[this_unit]  = repeat_count;
   lim[this_unit]  = limit;
   this_unit++;
}

void execute_command(void) {
   cindex i;
   int sym;

   ok = TRUE;
   switch (command & (~plusbit)) {

      case 'p':
      case 'P':
         printed = TRUE;
         i = lbeg;
         for (;;) {
            if (i == noted) {
               fprintf (tty_out, "*** Note ***");
               if (i == lbeg) fputc ('\n', tty_out);
            }
            if (i == pp) {
               if (i != lbeg) fputc ('^', tty_out);
               i = fp;
            }
            if (i == lend) break;
            sym = (*i++)&255;
            /* should re-order tests below for speed:
                (check 32 <= sym <= 126 first) */
            if (sym > 127) {
               fputc (sym, tty_out);/* possibly in inverse video highlight? */
            } else if ((sym < 32) || (sym == 127)) {
               fprintf (tty_out, "<%d>", sym);      /* or %2x ? */
            } else fputc (sym, tty_out);
         }
         if (i == fend) fprintf (tty_out, "*** End ***");
         fputc ('\n', tty_out);
         if (repeat_count == 1L) return;
         if ((command & minusbit) != 0) {
            move_back (); left_star();
         } else {
            move ();
         }
         return;

      case 'g':
      case 'G':
         local_echo (&sym);

         if (sym == ':') {
            local_echo (&sym);
            pending_sym = sym;
            if (sym != '\n')
               printed = TRUE;
            ok = FALSE;
            return;
         }
         left_star();
         for (;;) {
            if (pp == fp) /* FULL! */ { ok = FALSE; } else *pp++ = sym;
            if (sym == '\n') break;
            local_echo (&sym);
         }
         lbeg = pp;
         if ((command & minusbit) != 0) {
            move_back();
            printed = TRUE;
         }
         return;

      case 'E':
         if (fp == lend) {
            ok = FALSE;
            return;
         }
         if (repeat_count == 0L) {
            fp = lend;
            ok = FALSE;
         } else fp++;
         return;

      case 'e':
         if (pp == lbeg) {
            ok = FALSE;
            return;
         }
         if (repeat_count == 0L) {
            pp = lbeg;
            ok = FALSE;
         } else --pp;
         return;

      case 'C':
         if (fp == lend) {
            ok = FALSE;
            return;
         }
         sym = *fp++;
         if (('a' <= (sym | casebit)) && ((sym | casebit) <= 'z')) {
            if (caseflip != 0) {
               *pp++ = sym ^ casebit;
            } else {
               *pp++ = ((sym ^ casebit) | to_lower_case) & to_upper_case;
            }
         } else {
            *pp++ = sym;
         }
         return;

      case 'c':
         if (pp == lbeg) {
            ok = FALSE;
            return;
         }
         sym = *--pp;
         if (('a' <= (sym | casebit)) && ((sym | casebit) <= 'z')) {
            if (caseflip != 0) {
               *--fp = sym ^ casebit;
            } else {
               *--fp = ((sym ^ casebit) | to_lower_case) & to_upper_case;
            }
         } else {
            *--fp = sym;
         }
         return;

      case 'l':
      case 'R':
         if (repeat_count == 0L) {
            right_star();
            ok = FALSE;
         } else (void) right ();
         ms_back = NULL;
         return;

      case 'r':
      case 'L':
         if (repeat_count == 0L) {
            left_star();
            ok = FALSE;
         } else (void) left ();
         ms = NULL;
         return;

      case 'B':
         if (pp == fp) /* FULL! */ { ok = FALSE; return; }
         *pp++ = '\n';
         lbeg = pp;
         return;

      case 'b':
         if (pp == fp) /* FULL! */ { ok = FALSE; return; }
         *--fp = '\n';
         lend = fp;
         return;

      case 'J':
         right_star();
         if (fp == fend) {
            ok = FALSE;
            return;
         }
         lend = ++fp;
         while (*lend != '\n')
            lend++;
         return;

      case 'j':
         left_star();
         if (pp == fbeg) {
            ok = FALSE;
            return;
         }
         lbeg = --pp;
         do { --lbeg; } while (*lbeg != '\n');
         lbeg++;
         return;

      case 'M':
         if (repeat_count == 0L) {
            move_star();
            ok = FALSE;
         } else {
            move ();
         }
         return;

      case 'm':
         if (repeat_count == 0L) {
            move_back_star();
            ok = FALSE;
         } else {
            move_back(); left_star(); /* Edinburgh compatibility */
         }
         return;

      case 'k':
      case 'K':
         if ((command & minusbit) != 0) {
            move_back();
            if (!ok) return;
         }
         pp = lbeg;
         fp = lend;
         if (lend == fend) {
            ok = FALSE;
            return;
         }
         lend = ++fp ;
         while (*lend != '\n') lend++;
         return;

      case 'V':
         (void) verify ();
         return;

      case 'v':
         (void) verify_back ();
         return;

      case 'F':
         (void) find ();
         return;

      case 'f':
         (void) find_back ();
         return;

      case 'U':
         if (!find ()) return;
         pp = pp_before;
         lbeg = pp;
         do { --lbeg; } while (*lbeg != '\n');
         lbeg++;
         return;

      case 'u':
         if (!find_back ()) return;
         fp = fp_before;
         lend = fp;
         while (*lend != '\n')
            lend++;
         return;

      case 'D':
         if (!find ()) return;
         fp = ml;
         ms = fp;
         return;

      case 'd':
         if (!find_back ()) return;
         pp = ml_back;
         ms_back = pp;
         return;

      case 'T':
         if (!find ()) return;
         while (fp != ml) *pp++ = *fp++;
         return;

      case 't':
         if (!find_back ()) return;
         while (pp != ml_back) *--fp = *--pp;
         return;

      case 'I':
         insert ();
         return;

      case 'i':
         insert_back ();
         return;

      case 's':
      case 'S':
         if (fp == ms) {
            fp = ml;
         } else if (pp == ms_back) {
            pp = ml_back;
         } else {
            ok = FALSE;
            return;
         }
         if ((command & minusbit) != 0) {
            insert_back ();
         } else {
            insert ();
         }
         return;

      case '(':
         num[pointer] = repeat_count;
         repeat_count = 1L;
         return;

      case ')':
         --(num[this_unit]);
         if ((0 != num[this_unit]) && (num[this_unit] != stopper)) {
            this_unit = pointer;
         }
         repeat_count = 1L;
         return;

      case '\\':
         ok = FALSE;
         return;

      case '?':
         return;

      case ',':
         this_unit = pointer - 1;
         return;

      case 'N':
         noted = pp;
         changes = fp-pp;
         return;

      case 'A':
         if ((noted == NULL)
          || (noted >= pp)
          || (changes != fp-pp)) {                    /*BUG*/
            ok = FALSE;
            return;
         }
         note_file[CONTEXT_OFFSET] = lim[this_unit]+'0';
         {
            FILE *note_out = fopen (note_file, "wb");
            cindex p = noted;

            if (note_out == NULL) {
               ok = FALSE;
               return;
            }

            do {
               fputc (*p++, note_out);
            } while (p != pp);

            fclose (note_out);

            pp = noted;
            lbeg = pp;
            do { --lbeg; } while (*lbeg != '\n');
            lbeg++;
         }
         noted = NULL;
         return;

      case 'H':
         note_file[CONTEXT_OFFSET] = lim[this_unit]+'0';
         {
            FILE *note_in = fopen (note_file, "rb");
            if (note_in == NULL) {
               ok = FALSE;
               return;
            }

            { cindex p = pp;

               for (;;) {
                  sym = fgetc(note_in);
                  if (sym == EOF) break;
                  if (p == fp) {
                     ok = FALSE;
                     break;
                  }
                  *p++ = sym;
               }
               pp = p;
            }
            lbeg = pp;
            do { --lbeg; } while (*lbeg != '\n');
            lbeg++;
            fclose (note_in);
         }
         return;

      default:
         (void) fail_with ("Unrecognised command", command);
         return;
   }
}

void Scan_sign(void) {
   read_sym ();
   if (sym_type[sym] == sym_type['+']) {
      command = command | plusbit;
   } else if ((sym_type[sym] == sym_type['-']) &&
            (('A' <= command) && (command <= 'Z'))) {
      command = command | minusbit;
   } else {
      pending_sym = sym;
   }
}

void Scan_scope(void) {                      /* ditto macro */
   number = 1L;
   if (('D' != (command && (~(minusbit | plusbit)))) &&
                ((command && (~(minusbit | plusbit))) != 'U')) number = 0L;
   read_item ();
   if ((type & numb) == 0) pending_sym = sym;
   limit = number;
   if (('H' == command) || (command == 'A')) {
      if (!((0L <= limit) && (limit <= 9L))) limit = '?'-'0';
   }
}
 
void Scan_text(void) {
   char last;

   read_sym ();
   last = sym;
   if ((sym_type[sym] & delim) == 0) {
      pending_sym = sym;
      (void) fail_with ("Text for", command);
      return;
   }
   if (('a' <= command) && (command <= 'z')) {
      text[endpos] = 0;
      for (;;) {
         local_echo (&sym);
         if (sym == last) break;
         if (sym == '\n') {
            pending_sym = '\n';
            break;
         }
         text[--endpos] = sym;
      }
      pointer = endpos--;
   } else {
      pointer = pos;
      for (;;) {
         local_echo (&sym);
         if (sym == last) break;
         if (sym == '\n') {
            pending_sym = '\n';
            break;
         }
         text[pos++] = sym;
      }
      text[pos++] = 0;
   }
   ok = TRUE;
}

void Scan_repeat (void) {
   number = 1L;
   read_item ();
   if ((type & numb) == 0) pending_sym = sym;
   repeat_count = number;
}

bool analyse (void) {
   int saved_type;

   ok = TRUE;
   pos = 0;
   endpos = Max_command_units;
   this_unit = 0;
   last_unit = -1;
   eprompt = com_prompt;
   do { read_item (); } while (type == sym_type[';']);
   command = sym;
   if (command == '%') {
      read_sym();
      if (sym_type[sym] == sym_type[';']) {
         pending_sym = sym;
         sym = 0;
      }
      percent (((('a' <= sym) && (sym <= 'z')) ? (sym - casebit) : sym  ));
      return (ok = FALSE); /* to inhibit execution */
   }
   if ((type & numb) != 0) {
      if (max_unit > 0) {
         num[max_unit] = number;
      } else {
         return (ok = FALSE);
      }
      read_item();
      if (type != sym_type[';'])
         (void) fail_with ("?", sym);
      pending_sym = sym;
      return (ok);
   }
   for (;;) {  /* on items */
      if ((type & err) != 0) {
         return (fail_with ("Command", command));
      }
      if ((type & delim) != 0) {
         return (fail_with ("Command before", command));
      }
      if ((type & numb) != 0) {
         return (fail_with ("Unexpected repetition count", command));
      }
      limit = 0L;
      pointer = 0;
      repeat_count = 1L;
      if ((type & ext) == 0) {
         saved_type = type;           /* All this needs a tidy-up */
         if ((saved_type & sign) != 0) Scan_sign ();
         if ((saved_type & scope) != 0) Scan_scope ();
         if ((saved_type & txt) != 0) Scan_text ();
         if (!ok) return (ok);
         if ((saved_type & rep) != 0) Scan_repeat ();
         type = saved_type;
      } else {
         switch (type & 15) {

            case termin:
               pending_sym = '\n';  /* for skipping on error */
               unchain ();
               if (pointer >= 0) {
                  return (fail_with ("Missing", ')'));
               }
               max_unit = this_unit;
               repeat_count = 1L;
               command = ')';
               stack ();
               command = 0;
               stack ();
               return (ok);

            case lpar:
               command = '(';
               pointer = last_unit;
               last_unit = this_unit;
               break;

            case comma:
               command = ',';
               pointer = last_unit;
               last_unit = this_unit;
               break;

            case rpar:
               command = ')';
               Scan_repeat ();
               unchain ();
               if (pointer < 0) {
                  return (fail_with ("Missing", '('));
               }
               num[pointer] = repeat_count;
               break;
         }
      }
      stack ();
      read_item ();
      command = sym;
   }  /* on items */
}

void load_file (void) {
   cindex p = fbeg;
   int sym;

   sym = fgetc(main_in);
   while (sym != EOF) {
       if (sym != '\r') { /* Ignore CR in CR/LF on DOS/Win */
        *p++ = sym;
        if (p == fend) {
           fprintf (stderr, "* File too large!\n");
           percent ('A');
        }
      }
      sym = fgetc(main_in);
   }
   fclose (main_in);

   while (p != fbeg) *--fp = *--p;
   lend = fp;
   while (*lend != '\n')
      lend++;
}

bool execute_unit (void) {
   char culprit;

   command = com[this_unit];
   culprit = command;
   pointer = link[this_unit];

   repeat_count = num[this_unit];
   for (;;) {  /* On repeats of this_unit */
      if (IntSeen) {
        return (ok = FALSE);
      }
      execute_command ();
      --repeat_count;
      if (ok) {
         if (repeat_count == 0L || repeat_count == stopper) {
           return (ok);
         }
         continue;
      }
      ok = TRUE;
      for (;;) {  /* scanning for end of unit (e_g_ ')') */
         if (IntSeen) {
           return (ok = FALSE);
         }
         if (repeat_count < 0L ) {
           if (com[this_unit+1] == '\\') {
              this_unit++;
              return (ok = FALSE);
           }
           return (ok);
         }
         if ((com[this_unit+1] == '\\') || (com[this_unit+1] == '?')) {
            this_unit++;
            return (ok);
         }
         /* indefinite repetition never fails */
         for (;;) {  /* scanning for end of sequence */
            if (IntSeen) {
              return (ok = FALSE);
            }
            this_unit++;
            command = com[this_unit];
            switch (command) {

               case '(':
                  this_unit = link[this_unit];
                  break; /* Skip over (...) as if it were single command. */

               case ',':
                  return (ok);

               case ')': /* Should test for '\\' and '?' following? */
                  --num[this_unit];
                  repeat_count = num[this_unit];
                  /* A bug was fixed here: something got lost in the
                     translation from BCPL to C -- the line below was
                     a 'break' which unfortunately broke out of the
                     enclosing case statement rather than the desired
                     for-loop! */
                  /* rely on enclosing for-loop to handle \ and ? correctly! */
                  goto breaklab;

               default: /* Possible bugfix - what happens on missing cases? */;
            }
            if (com[this_unit] == 0) {/* 0 denotes end of command-line. */
               return (fail_with ("Failure:", culprit));
            }
         }  /* end of seq */
         breaklab: ;
      }  /* find () ')' without \ or ? */
   } /* executing repeats */
}

void execute_all (void) {
   eprompt = ":";
   this_unit = 0;
   do {
      if (!execute_unit()) {
      	return;
      }
      if (IntSeen) {
        return;
      }
      this_unit++;
   } while (com[this_unit] != 0);
   ok = TRUE;
}

/* All of the following could be static inlines under GCC, or
   I might recode some of them as #define'd macros */

char case_op (char sym) {               /* should be made a macro */
int chr = sym | casebit;
   if (('a' <= chr) && (chr <= 'z')) sym = (sym | to_lower_case)
                                                & to_upper_case;
   return (sym);
}

bool right (void) {
   if (fp == lend) {
      return (ok = FALSE);
   }
   *pp++ = *fp++;
   return (ok = TRUE);
}

bool left (void) {
   if (pp == lbeg) {
      return (ok = FALSE);
   }
   *--fp = *--pp;
   return (ok = TRUE);
}

void right_star(void) {                      /* Another macro */
   while (fp != lend) *pp++ = *fp++;
}

void left_star(void) {                       /* Likewise... */
   while (pp != lbeg) *--fp = *--pp;
}

void move (void) {
   ok = TRUE;
   right_star ();
   if (fp == fend) {
      ok = FALSE;
      return;
   }
   *pp++ = *fp++;
   lbeg = pp;
   lend = fp;
   while (*lend != '\n') lend++;
   ms_back = NULL;
}

void move_back(void) {
   ok = TRUE;
   left_star ();
   if (pp == fbeg) {
      ok = FALSE;
      return;
   }
   *--fp = *--pp;
   lend = fp;
   lbeg = pp;
   do { --lbeg; } while (*lbeg != '\n');
   lbeg++;
   ms = NULL;
}

void move_star (void) {
   while (fp != fend) *pp++ = *fp++;
   lend = fend;
   lbeg = pp;
   do { --lbeg; } while (*lbeg != '\n');
   lbeg++;
   ms_back = NULL;
}

void move_back_star (void) {
   while (pp != fbeg) *--fp = *--pp;
   lbeg = fbeg;
   lend = fp;
   while (*lend != '\n')
      lend++;
   ms = NULL;
}

void insert (void) {
   int p = pointer;
   ml_back = pp;
   while (text[p] != 0) {
     if (pp == fp) /* FULL! */ { ok = FALSE; break; }
     *pp++ = text[p++];
   }
   ms_back = pp;
   ms = NULL;
}

void insert_back (void) {
   int p = pointer;
   ml = fp;
   while (text[p] != 0) {
     if (pp == fp) /* FULL! */ { ok = FALSE; break; }
     *--fp = text[p++];
   }
   ms = fp;
   ms_back = NULL;
}

bool verify (void) {
   int x = pointer;
   cindex y = fp-1;
   char if_sym;
   char sym ;

   do {
      sym = case_op (text[x++]);
      if_sym = case_op (*++y);
   } while (sym == if_sym);

   if (sym != 0) return (ok = FALSE);

   ms = fp;
   ml = y;
   ms_back = NULL;

   return (ok = TRUE);
}

bool verify_back (void) {
   int x = pointer - 1;
   int y = 0;
   char if_sym;
   char sym;

   do {
      sym = case_op (text[++x]);
      if_sym = case_op (*(pp - ++y));
   } while (sym == if_sym);

   if (sym != 0) return (ok = FALSE);

   ms_back = pp;
   ml_back = pp - y + 1;
   ms = NULL;

   return (ok = TRUE);
}

bool find (void) {
   char sym = text[pointer] | casebit;

   pp_before = pp;
   limit = lim[this_unit];
   if (fp == ms) {
      if (!(right ())) move ();
   }
   for (;;) {
      if ((*fp | casebit) == sym) {
         if (verify ()) return (ok);
      }
      if (!right ()) {
         --limit;
         if (limit == 0L) break;
         move ();
         if (!ok) break;
      }
   }

   return (ok = FALSE);
}

bool find_back (void) {
   fp_before = fp;
   limit = lim[this_unit];
   if (pp == ms_back) {
      if (!left ()) move_back ();
   }
   for (;;) {
      if (verify_back ()) return(ok);
      if (!left ()) {
         --limit;
         if (limit == 0L) break;
         move_back ();
         if (!ok) break;
      }
   }

   return (ok = FALSE);
}