#if __GNUC__ < 3

/* Current Ecce source does not compile on the ancient compiler
   on SunOS gelss2.in 4.1.3_U1 2 sun4c with gcc version 2.8.1
   Differences are very minor: */

/* doesn't have strtoul. I guess it doesn't matter as the parameter
   is never going to be > MAXINT */
#define strtoul strtol

/* the other SEEK_'s are present but not this one */
#define SEEK_END 2

/* *really* old compiler doesn't have 'const'.*/
#define const

/* This will work as long as long ints are
   declared as "long" and not "long int": */
#define long int

#endif

/*  TO DO: ecce commands passed in on the command-line
    do not work as expected - the command-line has to
    be converted using mbstowcs or whatever, so that
    unicode text can be given in the command line and
    u8 strings will match the u32 data.
 */

/*
   I recently had to compile ecce on an old Sparc, and in doing
   so found that some of the newer code stopped it from compiling
   on the old gcc compiler.  Since the changes were fairly
   gratuitous and easily undone, I should apply them here too.
   (I'll need to fetch the edited copy of ecce.c off that old
    sparc).
  
   Once that is done,

   1) make it possible to switch between unicode/8-bit at run
   time, for those files where ecce reports a bad unicode
   character and fails to load the file.  We should *always*
   be able to edit binary files with ecce regardless of content
   (taking care not to mangle cr/lf pairs if a binary file)
  
   and 2) add the 'z' command or something equivalent for underline
   flipping.
  
   3) When we have (2) above, need to modify the "(r,m)<N>" hack
   when invoked from Emacs to position the cursor at the same place
   on both entry and exit.
  
   4) maybe add a mechanism to filter keystrokes in some way so
   that multi-key sequences (eg in Algol or Atlas Autocode) are
   replaced on the fly.  Actually that would be better done in
   EMACS. Same goes for piping a block through a filter, although
   ecce could do that quite cleanly with something like N M A3 H3/indent/
*/

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

   UTF support is as follows (when enabled):
   All I/O (file and command-line) is UTF8.
   All internal storage (text in the file, and
   string parameters to ecce commands) is in UTF32.
   UTF32 'wchar_t' is used in place of old-fashioned
   'char' throughout.  This way characters can be
   stepped over in the editor with a simple ++ or --
   rather than having to recognise the start of a
   UTF8 multibyte sequence - which might have been
   OK for moving forward though text, but is an
   unparsable nightmare when moving backwards through text.

   Although this started off as reasonably portable C,
   there are now a couple of places where linux filenames
   and some other assumptions (such as 4-byte wchar_t's) 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, but was left in because the alternatives
   are all extremely system dependent whereas tmpnam
   was available even on Windows C libraries.

   Revision history.

V2.10c and V2.10d changes are *NOT* YET UPLOADED TO SOURCEFORGE. (And remove this comment when I do
upload it.) I should test for a bit longer.  Especially case stuff and using utf8 in strange places.


   V2.10d small fixes to avoid namespace pollution problems
   with the Cosmopolitan CC compiler and APE environment.
   Also an undefined behavior was found and corrected - a
   call to fopen with "rw" which should have been "rb" - not
   even "r+" - there was no need for it to be writable (unless
   I was doing it as an early check to make sure the edited
   file could be written back out, but since I wasn't checking
   the result anyway, I doubt that was the case).


   V2.10c adds a way for the ecce command to report failure
   on the emacs display (it's in the comment about .emacs).  Also
   the 'g' and %W commands are suppressed because ecce is not running
   in an interactive buffer reads data from stdin, and I currently
   have no way to call back to emacs to allow interactive data entry...
   Also testing a fix for wide characters which can be 4 bytes
   and not just 2 as assumed in one place in the code (2 byte
   wide characters were a limitation of Windows/DOS).

   V2.10b makes some small portability improvements (/ * * / comments),
   fixes a careless bug (/dev/shm/Note0 instead of /tmp/Note0),
   and marks the code with *SYS* in places where portability
   might be an issue.

   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.
   If -hex-command is used, we assume ecce is being invoked
   from Emacs and is no longer capable of interaction.

   The version prior to this was stable at sourceforge for
   the previous 20 years.

*SYS*
Add this to your ~/.emacs file (or some equivalent for Windows).
NOTE we need ecce8 because Emacs handles wide characters, and
they're all over the damn place nowadays :-(
Also, the hack to extract the Ecce error message from the
output of the subshell is A) *very* hacky and B) not portable...
(and the temporary file should have a unique name - this is not
robust against multiple invocations of ecce simultaneously -
again, a single-user system is assumed.)


;; Embedded ECCE support. Invoked by ^E

;; Outstanding issues.  PLEASE READ.

;;  1) if failure to launch (eg not enough memory), failure causes buffer to empty
;;  2) difficult to send a ^C to ecce from Emacs, to kill long-running command (eg (p0m-0)0  )
;;  3) reissuing last ecce command using '1' or some other repeat number not implemented.
;;  4) no way to call back to emacs from inside ecce - would be nice to
;;     a) force screen update with 'p' command  (P+n could force current line to be displayed on screen line n)
;;     b) recursively pick up text arguments interactively, eg F S I T U D, maybe G
;;  5) Some issues with %A due to input being stdin
;;  6) Fixed buffer size, also due to input being stdin
;;  7) The mechanism for rediscovering 'point' is ugly.
;;  8) Uses bash, not sh.
;;  9) To interrupt, control-G, possibly twice. And then <esc-x>undo to recover the buffer.
;;     Don't say you were never told! (Sorry, I know it's revolting, but it's better than
;;     waiting forever or struggling to kill the process from the command-line...)
;; Despite the apparent long list of infelicities, it *is* fairly robust and eminently usable.

(defun uni2ascii (s)
  (mapconcat (lambda (c) (format "%02X" c))
                         (encode-coding-string s 'utf-8) ""))

(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 (format "ecce8 - - -size %d -hex-command " (* 4 (- (point-max) (point-min))))
                                 (concat (uni2ascii(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))
    (ignore-errors (search-forward (format "%%EMACS%dCURSOR%%" (+ 495620 oldpoint))))
    (replace-match "" nil nil)
  )
  (message (shell-command-to-string "grep \"^\* \" ~/.ecce-emacs.err | tail -1 | tr -d \"\\n\""))
)

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

 */

#define VERSION "V2.10d" /* %V */
 static const char *RCS_Version = "$Revision: 1.11 $"; /* only relevant to my home linux /usr/local/src/ecce */
#define DATE "$Date: 2025/12/12 19:57:57 $"

#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)
#ifdef WEOF
#undef WEOF // Because of namespace pollution in Cosmocc/APE library :-(
#endif
#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 ecce_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, "rb"); // was "rw" which is undefined behaviour.
  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
   for user input of ecce commands.  (Data in file is handled OK) */
#define    casebit         ('a'-'A')
/* plusbit and minusbit are only ever used with the 'command' variable,
   which has already been restricted to being a letter, a to z,
   or one of '(', ',', ')', '\' ... */
#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; /* I suppose it's a bit silly to use 32-bit unsigned longs just to get larger buffers. */
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*/  /* sign+rep if adding the 'Z' command! (see comment below re underlining) */
   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*/  /* sign+rep if adding the 'Z-' command! */
   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  *clink; // Should be 'link' but renamed clink because of namespace pollution in Cosmocc/APE library
static ecce_char *text;
static long *num;
static long *lim;

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

static int IntSeen = FALSE; /* set asynchronously by signal routine on ^C */
static int emacs_mode = FALSE; /* Currently used only to suppress 'g' command */

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);
  percent('A');
  exit(1);
}

ecce_char *utf8_to_utf32(char *eightbit) {
  /* Has to work even if utf not being used */
  int i;
  ecce_char *w = malloc((strlen(eightbit)+1) * sizeof(ecce_char)); /* This malloc is likely only ever called once per run. Acceptable heap lossage. */
#ifdef WANT_UTF8
  char *ptr = eightbit; /* ptr may be set to NULL, hence the copy */
  size_t rc = mbsrtowcs(w, (const char **)&ptr, strlen(eightbit)+1, NULL); /* +1 for terminating NUL. I *think* it is needed. */
  if (rc == -1) {
    if (errno == EILSEQ) {
      fprintf(stderr, "* invalid multibyte sequence: %s\n", eightbit);
    } else {
      fprintf(stderr, "* failed to perform multibyte to wide character conversion on: %s\n", eightbit);
    }
    percent('A');
    exit(1);
  }
#ifdef DEBUG
  fprintf(stderr, "UTF32(\"%s\") = ", eightbit);
  for (i = 0; i <= strlen(eightbit); i++) {
    fprintf(stderr, "%08lx ", w[i]);
  }
  fprintf(stderr, "\n");
#endif
#else /* everything is just a normal char */
  for (i = 0; i <= strlen(eightbit); i++) {
    w[i] = eightbit[i];
  }
#endif
  return w;
}

char *hex_to_utf8(char *hex) { /* This works even in 8-bit ascii mode */
  static char commandline[Max_parameter], *f, *t;
#ifdef DEBUG
  {FILE *debug = fopen("/tmp/hex.txt", "w");
    fprintf(debug, "Hex: [%s]\n", hex);
    fclose(debug);
  }
#endif
  if (strlen(hex)/2+3 >= Max_parameter) {
    fprintf(stderr, "%s: hex-command parameter was too long.\n", ProgName);
    percent('A');
    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);
      percent('A');
      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';

  /* The parameter decoding is a throwback to the BCPL version of this code
     and was written for me by a workmate. (I think Andy Bray?).  It should
     be replaced with something more consistent with modern linux parameter
     handling */
  
  /* 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_utf8(argv[argno+1]);
        commandp = utf8_to_utf32(parameter[C]);
        emacs_mode = TRUE;
      } 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 = utf8_to_utf32(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));
   clink = (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 ||
    clink == NULL || text == NULL || num == NULL || lim == NULL ||
    com_prompt == NULL) {
      fprintf (stderr, "Unable to claim buffer space\n");
      free_buffers();
      exit (40);
   }
#ifdef WANT_UTF8
   if (sizeof(ecce_char) != 4) {
     fprintf (stderr, "Warning: sizeof(ecce_char) = %d ... should be 4 for UTF32\n", (int)sizeof(ecce_char));
   }
   if (sizeof(ecce_int) != 4) {
     fprintf (stderr, "Warning: sizeof(ecce_int) = %d ... should be 4 for UTF32\n", (int)sizeof(ecce_int));
   }
#endif
   
   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 (clink) free (clink); clink = 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) { /* This needs to use wide characters to support UTF8 on the command-line */
      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 (emacs_mode) { /* same as below but with a failure */
          (void) fail_with ("not in Emacs mode", 'w');
          return;
        }
	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 (emacs_mode) {
           /* special case to avoid losing edit completely! */
           /* HOWEVER this is not an ecce '%a' - the changes will have been applied.
              Problem is that the file came via stdin and we don't have a copy of
              the original data to output.  So best we can hope for is a smart user
              and reversion using emacs */
           P = fbeg;
           for (;;) {
              if (P == pp) P = fp;
              if (P == fend) break;
              fputwc (*P++, stdout);
           }
           /* This report should make it all the way back up to Emacs */
           fprintf (stderr, "\n* YOU MUST EXECUTE <esc-x>undo TO REVERT THESE CHANGES!\n");
         }
         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 = clink[pointer];
      clink[pointer] = this_unit;
   } while (com[pointer] != '(');
}

void stack(void) {
   com[this_unit]  = command;
   clink[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; */  /* UTF8 supports 4 byte characters. UNTESTED
                                     Turns out 16 bit wide characters were a Windows/DOS-only restriction. */
#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':
         if (emacs_mode) {
           ok = FALSE; /* no way to enter data unfortunately. Maybe a better emacs programmer could find one. */
           return;
         }
         
         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;

         /*

            This ecce does not implement %X= %Y= %Z= macro definitions.  Since I usually use Ecce embedded
            within Emacs, it's better to use the Emacs keystroke macro mechanism.  So... the x,y,z
            commands are free to be added to ecce if we so wish.  I do have a version where I implemented
            a 'z' command which was syntactically similar to the 'c' command, except that rather than
            case-flipping an alphabetic character, it underlined it using the Unicode suffix character.
            (That code is at http://www.gtoal.com/editors/ecce/uecce.c )

            However it was a complex modification because the underline code *followed* the Unicode
            character, rather than making it into a new single character, so a lot of the text manipulation
            commands needed to be modified in complex ways.  I decided that - even though the mod seemed to
            work - it was too hairy to be worth the risk of incorporating into the mainline Ecce source,
            and anyway it was a facility that I suspect almost no-one except myself would ever use - it
            was created specifically for editing Algol 60 source code files in Unicode format, as per
            the demo at https://gtoal.com/recordings/algol-editing.html

          */
         
      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*/
        percent ('A');
	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 = clink[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 = clink[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) { /* Making more robust against UTF32 data */
   if ((('a' <= sym) && (sym <= 'z')) || (('A' <= sym) && (sym <= 'Z'))) {
     return (sym | to_lower_case) & to_upper_case;
   } else {
     return sym;
   }
#ifdef NEVER
   if (('a' <= (sym|32)) && ((sym|32) <= 'z')) sym = (sym | to_lower_case) & to_upper_case;
   return (sym);
#endif
}

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) {
  /* It has been a long-term bug that f// will find the next space
     character.  I've just left that in as it is fairly harmless,
     although if I now fix case matching (which I'm going to have
     to do, for Unicode) that 'feature' will go away. */
   ecce_int sym, fastcheck;
   sym = text[pointer];
   if ('A' <= sym && sym <= 'Z') sym |= casebit;

   pp_before = pp;
   limit = lim[this_unit];
   if (fp == ms) {
      if (!(right ())) move ();
   }
   for (;;) {
      fastcheck = *fp;
      if ('A' <= fastcheck && fastcheck <= 'Z') fastcheck |= casebit;
      if (fastcheck == 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);
}
