/* 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 -DWANT_UTF8 ecce.c

   You may need to do: export LC_ALL=en_US.UTF-8

   Although it's reasonable portable C, there are now
   a couple of places where linux filenames are used -
   you may need to make some small changes if compiling
   for Windows or other non-unix systems.  Look for *SYS*
   in the source below.  Feedback portability improvements
   to gtoal@gtoal.com please.  Note that we assume that
   even a linux system is single-user.  The use of
   tmpnam() for saving in an emergency is potentially
   unsafe in a multi-user environment if there are
   bad actors.

   Version 2.10a adds support for more robust embedding
   of ecce in Emacs, by making a version of the "-command"
   parameter (-hex-command) accept a hex string in order
   to pass the ecce command as a parameter while avoiding
   problems such as the use of " characters in the ecce command.


*SYS*
Add this to your ~/.emacs file (or some equivalent for Windows):

;; hex encoding needed below. Came from https://github.com/jwiegley/emacs-release/blob/master/lisp/hex-util.el
(eval-when-compile
  (defmacro num-to-hex-char (num)
    `(aref "0123456789abcdef" ,num))
  )

(defun encode-hex-string (string)
  "Encode octet STRING to hexadecimal string."
  (let* ((len (length string))
         (dst (make-string (* len 2) 0))
         (idx 0)(pos 0))
    (while (< pos len)
      (aset dst idx (num-to-hex-char (/ (aref string pos) 16)))
      (setq idx (1+ idx))
      (aset dst idx (num-to-hex-char (% (aref string pos) 16)))
      (setq idx (1+ idx) pos (1+ pos)))
    dst))

;; Embedded ECCE support

(defun e (ecce_command)
  (interactive "sEcce> ")
  (let (oldpoint (point))
    (setq oldpoint (point))
    (call-process-region (point-min) (point-max)
                         "/bin/bash"
                         t t nil
                         "-c"
                         (concat "ecce - - -hex-command "
                                 (concat (encode-hex-string (concat (concat (format "(r,m)%d(l,m-r0)?\n" (point)) ecce_command)
                                                                    (format "\ni/%%EMACS%dCURSOR%%/\n%%c" (+ 495620 oldpoint))
                                                                    )
                         ) " 2> ~/.ecce-emacs.err"))
    )
    (goto-char (point-min))
    (search-forward (format "%%EMACS%dCURSOR%%" (+ 495620 oldpoint)))
    (replace-match "" nil nil)
  )
)

(global-set-key "\C-e" 'e)

 */

#define VERSION "V2.10b" /* %V */
 static const char *RCS_Version = "$Revision: 1.4 $"; /* only relevant to my home linux /usr/local/src/ecce */
#define DATE "$Date: 2021/11/30 03:55:52 $"

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

#ifdef WANT_UTF8
/* EXPERIMENTAL SUPPORT FOR UTF-8 - been tested for a few years now, seems robust enough to make default. */
#include <wchar.h>
#include <locale.h>

typedef wint_t ecce_int;
typedef wchar_t ecce_char;
#else
typedef int ecce_int;
typedef char ecce_char;
#define fputwc(x,f) fputc(x,f)
#define fgetwc(f) fgetc(f)
#define WEOF EOF
#endif
/**************************************************/
/*                                                */
/*                                                */
/*                     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" /* Specific to Linux - unfortunately, /tmp is shared by other users */
             /* so this version of Ecce is really only expected to be used on a single-user system */

/* #define NOTE_FILE "/dev/shm/Note0" // Specific to the variation of Linux I'm using (ram disk)  *SYS*/

 /* I'm aware that this area of the code needs work to be made robust.  It looks like I can't
   have robustness without some OS-specific code.

   This code is already less portable than was first intended.
   Look for lines containing the marker *SYS* to see if the
   small deviations from portability affect your system.

   Note that the libraries with most windows C compilers try to handle
   some unix constructs such as / as a filename separator, so even some
   of the code marked *SYS* is likely to work on Windows.
*/

              /* 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 ecce_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

/* I know it is bad practise to have these fixed length arrays and I will
   work on that eventually.  I increased the size of these considerably
   when I modified Ecce to accept a command string as a parameter, because
   scripts were starting to need quite long command strings that were
   exceeding the inital bounds of 127 chars.  Again, we're assuming a
   non-hostile single-user environment. */
#define    Max_command_units 4095
#define    Max_parameter     4095
#define    Max_prompt_length 4095

#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 (ecce_int *sym);        /* Later, make this a char fn. */
void read_sym (void); 
bool fail_with (char *mess, ecce_int culprit); 
void percent (ecce_int 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); 
ecce_int case_op (ecce_int 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 ecce_int 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 ecce_int 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 ecce_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 symtype[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 int sym_type(ecce_char c) {
  if ((0 <= c) && (c <= 255)) return symtype[(unsigned int)c];
  return 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 ecce_int *com;
static int  *link;
static ecce_char *text;
static long *num;
static long *lim;

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

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

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

int h(char c) {
  if (('0' <= c) && (c <= '9')) return c - '0';
  if (('A' <= c) && (c <= 'F')) return c - 'A' + 10;
  if (('a' <= c) && (c <= 'f')) return c - 'a' + 10;
  fprintf(stderr, "%s: hex-command parameter corrupt - char '%c' is not hex\n", ProgName, c);
  exit(1);
}

char *hex_to_ascii(char *hex) {
  static char commandline[Max_parameter], *f, *t;
  if (strlen(hex)/2+3 >= Max_parameter) {
    fprintf(stderr, "%s: hex-command parameter was too long.\n", ProgName);
    exit(1);
  }
  f = hex; t = commandline;
  for (;;) {
    int c1,c2;
    if (*f == '\0') break;
    c1 = h(*f++);
    if (*f == '\0') {
      fprintf(stderr, "%s: hex-command parameter corrupt. (Odd no. of chars)\n", ProgName);
      exit(1);
    }
    c2 = h(*f++);
    *t++ = c1<<4 | c2;
  }
  *t = '\0';
  return commandline;
}

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;

#ifdef WANT_UTF8
  /* If your native locale doesn't use UTF-8 encoding 
   * you need to replace the empty string with a
   * locale like "en_US.utf8"
   */
  char *locale = setlocale(LC_ALL, "");
#endif

  backup_save = tmpnam(backup_save_buf);  /*SYS*/

  /* Historical code, not really needed nowadays as
     people only use Windows and Unix variants :-( */
  
  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 = malloc(strlen(s)+1); strcpy(ProgName, 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, "hex-command") == 0) {
        if (parameter[C] != NULL) {
          fprintf(stderr, "%s: only one -hex-command \"...\" or -command \"...\" is allowed\n", ProgName);
          exit(1);
        }
        parameter[C] = hex_to_ascii(argv[argno+1]); commandp = parameter[C];
      } else if (strcmp(argv[argno]+offset, "command") == 0) {
        if (parameter[C] != NULL) {
          fprintf(stderr, "%s: only one -command \"...\" or -hex-command \"...\" is allowed\n", ProgName);
          exit(1);
        }
        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') && (endptr[1] == '\0')) {
          /* memo: removed strcasecmp for portability. Also avoiding toupper etc for locale simplification */
          if (*endptr == 'k' || *endptr == 'K') {
            buffer_size *= 1024UL;
	      } else if (*endptr == 'm' || *endptr == 'M') {
            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}? {-{hex-}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)) {  /*SYS*/
      /* 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"); /*SYS*/
      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) * sizeof(ecce_char));

   note_file = malloc (Max_parameter+1);

   com  = (ecce_int *) malloc ((Max_command_units+1)*sizeof(ecce_int));
   link = (int *) malloc ((Max_command_units+1)*sizeof(int));
   text = (ecce_char *) malloc ((Max_command_units+1) * sizeof(ecce_char));

   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 (ecce_int *sym) {       /* Later, make this a char fn. */
   ecce_int lsym;

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

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

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

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

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

   if (log_out != NULL) {
      fputwc (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, ecce_int 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 %lc%c\n", mess, culprit, dirn_sign);
   do { read_sym (); } while (sym_type(sym) != sym_type(';'));
   return (ok = FALSE);
}


void read_item(void) {
   ecce_int saved_digit;
   read_sym ();
   if (isalpha(sym) && islower(sym)) sym = toupper(sym);
   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 (ecce_int Command_sym) {
   static int note_sec = '0'; /* This one MUST be a static */
   cindex P;
   int inoutlog;
   ecce_int sec_no;
   bool file_wanted; /* %s2 or %s2=fred ? */
   char sec_file[256], *sec_filep;
   ok = TRUE;
   if (!isalpha(Command_sym)) {
      (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 %s", VERSION);
#ifdef WANT_UTF8
         fprintf (tty_out, "/UTF8");
#endif
         fprintf (tty_out, " in C %s\n", DATE+7);
         break;

      case 'W':
	if ((strcmp(parameter[parameter[T] == NULL ? F : T], "-") == 0) ||
            ((parameter[T] != NULL) && (strcmp(parameter[T], "/dev/stdout") == 0))) { /*SYS*/
           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;
               fputwc (*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)) /*SYS*/
               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;
            fputwc (*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;
               fputwc (*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 = fgetwc(sec_in);
               if (sym == WEOF) 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;
   ecce_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++;
#ifdef WANT_UTF8
            sym &= 0xffff;
#else
            sym &= 0xff;
#endif
            if (sym > 127) {
	       /* Would use fputwc but it didn't output anything whereas %lc worked OK */
               fprintf (tty_out, "%lc", sym);
            } 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(); /* retain standard Edinburgh compatibility - my preference would have been to leave cursor at RHS */
         }
         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 {
               fputwc (*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 = fgetwc(note_in);
                  if (sym == WEOF) 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 */
   ecce_int uppercase_command = command & (~(minusbit | plusbit));
   if ((uppercase_command == 'D') || (uppercase_command == 'U')) number = 1L; else number = 0L;
   read_item ();
   if ((type & numb) == 0) pending_sym = sym;
   limit = number;
   if (('H' == uppercase_command) || (uppercase_command == 'A')) {
      if (!((0L <= limit) && (limit <= 9L))) limit = '?'-'0';
   }
}
 
void Scan_text(void) {
   ecce_int 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;
   ecce_int sym;

   sym = fgetwc(main_in);
   while (sym != WEOF) {
       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 = fgetwc(main_in);
#ifdef WANT_UTF8
      if (errno == EILSEQ) {
	fprintf(stderr, "An invalid wide character was encountered.  You may need to do: export LC_ALL=en_US.UTF-8\n");  /*SYS*/
	exit(1);                                                
      }
#endif
   }
   fclose (main_in);

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

bool execute_unit (void) {
   ecce_int 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 */

ecce_int case_op (ecce_int sym) {               /* should be made a macro */
   int chr = sym | casebit;
#ifdef NEED_TO_REWRITE_THIS_LATER
   if (isalpha(sym)) { /* this is a flip.  It doesn't yet support %L/%U/%N/%E */
     if (islower(sym)) {
       sym = toupper(sym);
     } else if (isupper(sym)) {
       sym = tolower(sym);
     }
   }
#else
   if (('a' <= chr) && (chr <= 'z')) sym = (sym | to_lower_case)
                                                & to_upper_case;
#endif
   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;
   ecce_int if_sym;
   ecce_int 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;
   ecce_int if_sym;
   ecce_int 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) {
   ecce_int 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);
}