/* 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); }