/****************************************************************************/
/*									    */
/*	 E D  :   T h e   C P / M   C o n t e x t    E d i t o r	    */
/*       -------------------------------------------------------            */
/*                                                                          */
/*      Copyright (c) 1976, 1977, 1978, 1979, 1980, 1981, 1982		    */
/*	    Digital Research						    */
/*	    Box 579 Pacific Grove					    */
/*	    California 93950						    */
/*									    */
/*	    Revised:							    */
/*	      07 April 81  by Thomas Rolander				    */
/*	      21 July  81  by Doug Huskey				    */
/*	      29 Oct   81  by Doug Huskey				    */
/*	      10 Nov   81  by Doug Huskey				    */
/*	      08 July  82  by Bill Fitler				    */
/*	      26 July  82  by Doug Huskey				    */
/*	       1 Aug   82  by Dave Sallume, Zilog Inc.			    */
/*	      12 Sept  82  by Dominic Dunlop, Zilog Inc.		    */
/*									    */
/*									    */
/*		**** this message should be in the header ****		    */
/*									    */
/* char copyright[] =							    */
/*	" Copyright (c) 1982, Digital Research ";			    */
/*									    */
/****************************************************************************/

char	date[] = "8/82";

/****************************************************************************/
/*									    */
/*		M O D I F I C A T I O N   L O G				    */
/*		-------------------------------				    */
/*									    */
/* Modified for .PRL operation			May, 1979		    */
/* Modified for operation with CP/M 2.0		August 1979		    */
/* Modified for MP/M 2.0			June 1981		    */
/* Modified for CP/M 1.1			October 1981		    */
/* Modified for concurrent CP/M 1.0		July 1982		    */
/* Modified for CP/M 3.0			July 1982		    */
/* Translated to C				August 1982		    */
/*									    */
/*   Aug 1982 Zilog: Translated to C.					    */
/*									    */
/*   July 1982 WHF: Some code cleanup (grouped logicals, declared bool);    */
/*        fixed disk full error handling; fixed read from null files;	    */
/*        fixed (some) of the dirty fcb handling (shouldn't use settype	    */
/*        function on open FCB's!).					    */
/*									    */
/*   July 1982 DH: Installed patches to change macro abort command from	    */
/*        ^C to ^Y and to not print error message when trying to delete	    */
/*        a file that doesn't exist.  Added perror: procedure to print	    */
/*        error messages in a consistant format and modified error	    */
/*        message handler at reset: entry point.  Also corrected invalid    */
/*        filename error to not abort ed if parsing a R or X command.	    */
/*        modified start and setdest to prompt for missing		    */
/*        filenames.  Modified parse_fcb & parse_lib to set a global	    */
/*        flag and break if it got an invalid filename for X or R commands. */
/*        Start sets page size from the system control block (SCB) if	    */
/*        ed is running under CP/M-80 (ver & 0xff00 == 0).		    */
/*        The H command now works with new files. (sets newfile = FALSE)    */
/*									    */
/****************************************************************************/

/****************************************************************************/
/*									    */
/*           P R O G R A M   D E S C R I P T I O N                          */
/*									    */
/*    Command           Function					    */
/*    -------           --------					    */
/*     A            Append lines of text to buffer			    */
/*     B            move to Beginning or end of text			    */
/*     C            skip Characters					    */
/*     D            Delete characters					    */
/*     E            End of edit						    */
/*     F            Find string in current buffer			    */
/*     H            move to top of file (Head)				    */
/*     I            Insert characters from keyboard			    */
/*		    up to next <ENDFILE>				    */
/*     J            Juxtaposition operation - search for first string,	    */
/*		    insert second string, delete until third string	    */
/*     K            delete (Kill) lines					    */
/*     L            skip Lines						    */
/*     M            Macro definition (see comment below)		    */
/*     N            find Next occurrence of string			    */
/*		      with auto scan through file			    */
/*     O            re-edit Old file					    */
/*     P            Page and display (moves up or down 23 lines and	    */
/*		      displays 24 lines)				    */
/*     Q            Quit edit without updating the file			    */
/*     R<filename>  Read from file <filename> until <ENDFILE> and	    */
/*		      insert into text					    */
/*     S            Search for first string, replace by second string	    */
/*     T            Type lines						    */
/*     U            translate to Upper case (-u changes to no translate)    */
/*     V	    Verify (print) line numbers (-v turns them off)	    */
/*     W            Write lines of text to file				    */
/*     X<filename>  transfer (Xfer) lines to file <filename>		    */
/*     Z            sleep for 1/2 second (used in macros to stop display)   */
/*     <CR>         move up or down and print one line			    */
/*									    */
/*									    */
/*   In general, the editor accepts single letter commands with optional    */
/*   integer values preceding the command.  The editor accepts both upper   */
/*   and lower case commands and values, and performs translation to upper  */
/*   case under the following conditions.  If the command is typed in	    */
/*   upper case, then the data which follows is translated to upper case.   */
/*   Thus, if the "I" command is typed in upper case, then all input is	    */
/*   automatically translated (although echoed in lower case, as typed).    */
/*   If the "A" command is typed in upper case, then all input is	    */
/*   translated as read from the disk.  Global translation to upper case    */
/*   can be controlled by the "U" command (-U to negate its effect).	    */
/*   If you are operating with an upper case only terminal, then operation  */
/*   is automatic.  Similarly, if you are operating with a lower case	    */
/*   terminal, and translation to upper case is not specified, then lower   */
/*   case characters can be entered.					    */
/*									    */
/*   A number of commands can be preceded by a positive or		    */
/*   negative integer between 0 and 65535 (1 is default if no value	    */
/*   is specified).  This value determines the number of times the	    */
/*   command is applied before returning for another command.		    */
/* 	The commands							    */
/*		       C D K L T P U V <CR>				    */
/*   can be preceded by an unsigned, positive, or negative number,	    */
/*   the commands							    */
/*		       A F J N W Z					    */
/*   can be preceded by an unsigned or positive number,			    */
/*   the commands							    */
/*		       E H O Q						    */
/*   cannot be preceded by a number.  The commands			    */
/*		       F I J M R S					    */
/*   are all followed by one or more strings of characters which can	    */
/*   be optionally separated or terminated by either <ENDFILE> or <CR>.	    */
/*   The <ENDFILE> is generally used to separate the search strings	    */
/*   in the S and J commands, and is used at the end of the commands if	    */
/*   additional commands follow.  For example, the following command	    */
/*   sequence searches for  the string 'GAMMA', substitutes the string	    */
/*   'DELTA', and then types the first part of the line where the	    */
/*   change occurred, followed by the remainder of the line which was	    */
/*   changed:								    */
/*		 SGAMMA<ENDFILE>DELTA<ENDFILE>0TT<CR>			    */
/*									    */
/*   The control-L character in search and substitute strings is	    */
/*   replaced on input by <CR><LF> characters.  The control-I key	    */
/*   is taken as a TAB character.					    */
/*									    */
/*	The commands R & X must be followed by a file name (with default    */
/*   file type of 'LIB') with a trailing <CR> or <ENDFILE>.  The command    */
/*   I is followed by a string of symbols to insert, terminated by	    */
/*   a <CR> or <ENDFILE>.  If several lines of text are to be inserted,	    */
/*   the I can be directly followed by an <ENDFILE> or <CR> in which	    */
/*   case the editor accepts lines of input to the next <ENDFILE>.	    */
/*   The command 0T prints the first part of the current line,		    */
/*   and the command 0L moves the reference to the beginning of the	    */
/*   current line.  The command 0P prints the current page only, while	    */
/*   the command 0Z reads the console rather than waiting (this is used	    */
/*   again within macros to stop the display - the macro expansion	    */
/*   stops until a character is read.  If the character is not a break	    */
/*   then the macro expansion continues normally).			    */
/*									    */
/*	Note that a pound sign (#) is taken as the number 65535, all	    */
/*   unsigned numbers are assumed positive, and a single - is assumed -1    */
/*									    */
/*   A number of commands can be grouped together and executed		    */
/*   repetitively using the macro command which takes the form		    */
/*									    */
/*	     <number>Mc1c2...cn<delimiter>				    */
/*									    */
/*   where <number> is a non-negative integer n, and <delimiter> is	    */
/*   <ENDFILE> or <CR>.  The commands c1 ... cn  following the M are	    */
/*   executed n times, starting at the current position in the buffer.	    */
/*   If n is 0, 1, or omitted, the commands are executed until the end	    */
/*   of the buffer is encountered.					    */
/*									    */
/*   The following macro, for example, changes all occurrences of	    */
/*   the name 'gamma' to 'delta', and prints the lines which		    */
/*   were changed:							    */
/*									    */
/*		 MFgamma<ENDFILE>-5IDdelta<ENDFILE>0LT<CR>		    */
/*									    */
/*  (Note: an <ENDFILE> is the CP/M end of file mark - control-Z)	    */
/*									    */
/*  If any key is depressed during typing or macro expansion, the	    */
/*  function is considered terminated, and control returns to the	    */
/*  operator.								    */
/*									    */
/*  Error conditions are indicated by printing one of the characters:	    */
/*									    */
/*   symbol                    error condition				    */
/*   ------      ----------------------------------------------------	    */
/*   greater     free memory is exhausted - any command can be issued	    */
/*     (>)	 which does not increase memory requirements.		    */
/*									    */
/*   question    unrecognized command or illegal numeric field		    */
/*     (?)								    */
/*									    */
/*   pound       cannot apply the command the number of times specfied	    */
/*     (#)	 (occurs if search string cannot be found)		    */
/*									    */
/*   letter O    cannot open <filename>.LIB in R command		    */
/*									    */
/*   The error character is also accompanied by the last character	    */
/*   scanned when the error occurred.					    */
/*									    */
/****************************************************************************/

/****************************************************************************/
/*									    */
/*	    H E A D E R   F I L E S ,    C O N F I G U R A T I O N          */
/*	    ------------------------------------------------------          */
/*									    */
/****************************************************************************/

#include "portab.h"				/* Portable C declarations  */
#include "bdos.h"				/* BDOS functions	    */
#include "basepage.h"				/* CP/M basepage layout	    */
#include "copyrt.lit"
#include "setjmp.h"				/* Used for error recovery  */

#define	MPMPRODUCT	0x01			/* requires MP/M	    */
#define	CPM3		0x30			/* requires 3.0 CP/M	    */
#define	SECTSIZE	0x80			/* sector size		    */


extern int	setjmp(), longjmp();		/* Non-local goto for errors*/
extern char	*brk(), *sbrk();		/* Memory allocation	    */


/****************************************************************************/
/*									    */
/*	  G L O B A L   V A R I A B L E S ,    D E F I N E S		    */
/*	  --------------------------------------------------		    */
/*									    */
/****************************************************************************/


#define	SFCB	fcb1				/* source fcb = default fcb */
						/*   (see below)	    */
#define MARGIN  0x400				/* # of bytes between stack */
						/*   & top of edit buffer   */

#define CTL_C   0x03				/* Control C: reboot cmd    */
#define	CTL_H	0x08				/* backspace		    */
#define	TAB	0x09				/* tab character 	    */
#define	LF	0x0a				/* Line-feed		    */
#define CTL_L	0x0c				/* Form-feed		    */
#define	CR	0x0d				/* Carriage return	    */
#define	CTL_R	0x12				/* insert mode repeat line  */
#define	CTL_U	0x15				/* insert mode line delete  */
#define	CTL_X	0x18				/* equivalent to CTL_U	    */
#define	CTL_Y	0x19				/* used as 'break' command  */
#define	ESC	0x1b				/* escape character	    */
#define	ENDFILE	0x1a				/* CP/M end of file (ctl-Z) */
#define	POUND	'#'				/* Taken to mean 65535	    */
#define	WHAT	'?'				/* Error message (see above)*/
#define	LCA	'a'				/* lower case a		    */
#define	LCZ	'z'				/* lower case z		    */
#define	RUBOUT	0x7f				/* line kill during insert  */

#define PASSLEN 8				/* Length of password	    */

#define	FORWARD		1
#define	BACKWARD	0
#define	MACSIZE		128			/* max macro size	    */
#define	SCRSIZE		100			/* scratch buffer size	    */

#define RESTART		0			/* Error codes used in	    */
#define GET_LINE	1			/*   longjmp calls to error */
#define OVERCOUNT	2			/*   handler in main()	    */
#define BADCOM		3
#define OVERFLOW	4
#define DISK_ERR	5
#define DIR_ERR		6
#define RESET		7

#define TIME		3500			/* See time() function below*/


/****************************************************************************/
/*                                                                          */
/* The two following variables are initialized on startup to point to the   */
/* basepage structures.                                                     */
/*                                                                          */
/****************************************************************************/

struct fcbtab	*fcb1;				/* 1st basepage FCB	    */
char	*buff;					/* Basepage DMA buffer	    */	



jmp_buf	main_env;				/* Used in error recovery   */

char	*base;					/* Address of edit buffer   */
UWORD	max;					/* base[max] is top of mem  */
UWORD	maxm;					/* base[maxm] is last usable*/
UWORD	hmax;					/* max/2 (halfway up memory)*/

struct fcbtab	rfcb	=			/* reader file control block*/
	{
		0,				/* "disk"		    */
		' ', ' ', ' ', ' ',
		' ', ' ', ' ', ' ',		/* filename		    */
		'L', 'I', 'B',			/* filename extension	    */
		0,				/* extent		    */
		0,				/* (reserved)		    */
		0				/* (reserved)		    */
	};

UWORD	rbp;					/* index into read buffer    */

struct fcbtab	xfcb	=			/* xfer file control block  */
	{
		0,
		'X', '$', '$', '$',
		'$', '$', '$', '$',		/* filename		    */
		'L', 'I', 'B',			/* filename extension	    */
		0,				/* extent		    */
		0,				/* (reserved)		    */
		0,				/* (reserved)		    */
		0,				/* record count		    */
		0, 0, 0				/* (more reserved stuff)    */
	};

BYTE	xfcbext	= 0;				/* save extent for appends  */
LONG	xfcbrec	= 0;				/* save record for appends  */

char	xbuff[SECTSIZE];			/* xfer buffer		    */
UWORD	xbp;					/* xfer index		    */

UWORD	nbuf;					/* number of buffers	    */
UWORD	bufflength;				/* nbuf * SECTSIZE	    */

char	*sbuffadr;				/* source buffer address    */

char	pwd[16];				/* source password	    */

struct fcbtab	dfcb;				/* dest file control block  */

char	*dbuffadr;				/* destination buff. address*/

UWORD	nsource;				/* next source char index   */
UWORD	ndest;					/* next dest char index	    */

struct fcbtab	tmpfcb;				/* temp. for rename & delete*/

BOOLEAN	newfile		= FALSE;		/* TRUE if no source file   */
BOOLEAN onefile		= TRUE;			/* TRUE if o/p file=i/p file*/
BOOLEAN xferon		= FALSE;		/* TRUE if xfer active	    */
BOOLEAN printsuppress	= FALSE;		/* TRUE if print suppressed */
BOOLEAN sys		= FALSE;		/* TRUE if system file	    */
BOOLEAN protection	= FALSE;		/* Password protection mode */
BOOLEAN inserting;				/* TRUE if inserting chars. */
BOOLEAN readbuff;				/* TRUE if end of read buff.*/
BOOLEAN translate	= FALSE;		/* TRUE if xlation to u/c   */
BOOLEAN upper		= FALSE;		/* TRUE if global xlate u/c */
BOOLEAN lineset		= TRUE;			/* TRUE if line #'s printed */
BOOLEAN has_bdos3	= FALSE;		/* TRUE if bdos vers. >= 3.0*/
BOOLEAN tail		= TRUE;			/* TRUE if reading cmd tail */
BOOLEAN dot_found	= FALSE;		/* TRUE if . found in parse */

char	dtype	[3];				/* destination file type    */

struct fcbtab	libfcb	=			/* default lib name	    */
	{
		0,
		'X', '$', '$', '$',
		'$', '$', '$', '$',		/* filename		    */
		'L', 'I', 'B'			/* filename extension	    */
	};

char	tempfl	[]	= "$$$";		/* temporary file type	    */
	
char	backup	[]	= "BAK";		/* backup file type	    */

int	column		= 0;			/* console column position  */
int	scolumn		= 8;			/* start column in "i" mode */

int	dcnt;					/* CP/M call return code    */



struct	con					/* console command buffer   */
	{
		char	maxlen, comlen, comline[128], cbp;
	} combuf =
	{
			0, 0, {0}, 0
	};

/* line counters */

UWORD	baseline;				/* current line		    */
UWORD	relline;				/* relative line in typeout */


char	macro[MACSIZE];				/* Macro command buffer	    */
char	scratch[SCRSIZE];			/* scratch space for F, N, S*/
UWORD	wbp, wbe;				/* end of F , S or J string */
char	flag;					/* tracks current context   */
UWORD	mp;					/* index to end of macro    */
UWORD	mi;					/* "miscellaneous index"    */
UWORD	xp;					/* index into macro buffer  */

UWORD	mt;					/* number of times for macro*/


/* Global variables used by file parsing routines			    */

UWORD	ncmd	= 0;				/* Index into command buffer*/

UWORD	distance;				/* # of lines changed by cmd*/
int	direction;				/* FORWARD or BACKWARD	    */
int	chr;					/* Char being processed now */
int	delimiter;				/* Latest delimiter char    */
UWORD	front, back, first, lastc;		/* indices to edit buffer   */

int	lpp	= 23;				/* lines per "page" (screen)*/


char	pb [2]	=				/* Argument to CPM 3 when   */
	{					/*   reading lines/page	    */
		28, 0
	};

int	ver;					/* version number	    */

char	*err_msg;				/* File handling error msgs */
char	diskfull[]	= "disk full$";
char	dirfull[]	= "directory full$";
char	not_found[]	= "file not found$";
char	invalid[]	= "invalid filename$";
char	pwd_err[]	= "creating password$";
char	notavail[]	= "file not available$";


/****************************************************************************/
/*									    */
/*		C P / M   I N T E R F A C E   R O U T I N E S		    */
/*		---------------------------------------------		    */
/*									    */
/****************************************************************************/




		/********************************/
		/*		 		*/
		/*  I / O   S E C T I O N	*/
		/*		 		*/
		/********************************/

		/********************************/
		/*		 		*/
		/*        P R I N T C H         */
		/*		 		*/
		/********************************/
VOID
printch(ch)					/* Single char console o/p  */
char ch;
{
	if (printsuppress)
		return;
	_conout(ch);
}


		/********************************/
		/*		 		*/
		/*        T T Y C H             */
		/*		 		*/
		/********************************/
VOID
ttych(ch)					/* Single char o/p, keeping */
char	ch;					/*  track of current column */
{
	if (ch >= ' ')
		column++;
	if (ch == LF)
		column = 0;
	printch(ch);
}


		/********************************/
		/*		 		*/
		/*       B A C K S P A C E      */
		/*		 		*/
		/********************************/
VOID
backspace()					/* Overprint last displayed */
{						/*  char with space, move   */
	if (column == 0)			/*  cursor to that position */
		return;
	ttych(CTL_H);				/* column = column - 1	    */
	ttych(' ');				/* column = column + 1	    */
	ttych(CTL_H);				/* column = column - 1	    */
	column -= 2;
}


		/********************************/
		/*		 		*/
		/*      P R I N T A B S         */
		/*		 		*/
		/********************************/
VOID
printabs(ch)					/* Print a character	    */
char	ch;
{
	 register int j;
						/* Expand tabs (tabs are    */
	if (ch == TAB)				/*   fixed at every eight   */
	{					/*   columns - 0, 8, 16 etc)*/
		do 
			ttych(' ');
		while (column & 7);
	}
	else
		ttych(ch);
}


		/********************************/
		/*		 		*/
		/*        G R A P H I C         */
		/*		 		*/
		/********************************/
BOOLEAN
graphic(ch)					/* TRUE if ch is printable  */
char	ch;
{
	if (ch >= ' ')
		return(TRUE);
	return ((ch==CR) | (ch==LF) | (ch==TAB));
}


		/********************************/
		/*		 		*/
		/*        P R I N T C           */
		/*		 		*/
		/********************************/
VOID
printc(ch)					/* Print character, 	    */
char	ch;					/*   presenting non-print   */
{						/*   control chars as	    */
	if (! graphic(ch))			/*   ^<letter>		    */
	{					/* For example, control-A   */
		printabs('^');			/* prints as ^A		    */
		ch += '@';
	}
	printabs(ch);
}


		/********************************/
		/*		 		*/
		/*           C R L F            */
		/*		 		*/
		/********************************/
VOID
crlf()						/* Move to new line	    */
{						/* (current column set to   */
	printc(CR);				/* zero by ttych()	    */
	printc(LF);
}


		/********************************/
		/*		 		*/
		/*           P R I N T          */
		/*		 		*/
		/********************************/
VOID
print(a)					/* Go to new line, then	    */
char	*a;					/*   print a string 	    */
{
	crlf();
	_print(a);
}


		/********************************/
		/*		 		*/
		/*         P E R R O R          */
		/*		 		*/
		/********************************/
VOID
perror(a)					/* Print an error message   */
char	*a;
{
	print("\tError - $");
	_print(a);
	crlf();
}


		/********************************/
		/*		 		*/
		/*          O P E N             */
		/*		 		*/
		/********************************/
int
open(fcb)					/* Open a file - used for   */
struct fcbtab	*fcb;				/*   library files	    */
{						/* Restart ED from the top  */
	if (_open(fcb) == 0xff)			/*   after printing error   */
	{					/*   message if operation   */
		flag = 'O';			/*   fails		    */
		err_msg = not_found;
		longjmp(main_env, RESET);
	}
}


		/********************************/
		/*		 		*/
		/*         R E A D C O M        */
		/*		 		*/
		/********************************/
VOID
readcom()					/* Read console input line  */
{						/*   into combuf	    */
	combuf.maxlen = 128;
	_conbuf(&combuf);
}


		/********************************/
		/*		 		*/
		/*      B R E A K _ K E Y       */
		/*		 		*/
		/********************************/
BOOLEAN	
break_key()					/* Return TRUE if character */
{						/*   entered at console is  */
	return ((_constat() != 0)		/*   CTL_Y.  If not CTL_Y   */
	   && (_conio(0xff) == CTL_Y));		/*   or no char entered,    */
}						/*   return FALSE.  Any	    */
						/*   character read is lost */


		/********************************/
		/*		 		*/
		/*          M O V E             */
		/*		 		*/
		/********************************/
VOID
move(count, source, dest)			/* Move count bytes from    */
register char	*source, *dest;			/*   source to dest.  Useful*/
register UWORD	count;				/*   where types not suited */
{						/*   for struct assignment  */
	for (; count--; *dest++ = *source++);
}


		/********************************/
		/*		 		*/
		/*      W R I T E _ X F C B     */
		/*		 		*/
		/********************************/
VOID
write_xfcb(fcb)					/* Write an extended FCB    */
struct fcbtab	*fcb;
{
	move(8, pwd, &pwd[8]);
	if (_set_xfcb(fcb) == 0xff)
		perror(pwd_err);
}


		/********************************/
		/*		 		*/
		/*         R E B O O T          */
		/*		 		*/
		/********************************/
VOID
reboot()					/* CP/M warm start following*/
{						/*   error or on request    */
	if (xferon == TRUE)			/*   (user enters control-C)*/
		_delete(&libfcb);	
	_exit(0);
}

		/********************************/
		/*		 		*/
		/*            T I M E           */
		/*		 		*/
		/********************************/

VOID						/* Provide a delay of about */
time()						/*   25 milliseconds.  Tune */
{						/*   for processor, compiler*/
	register UWORD	downer;			/*   and clock rate with    */
						/*   defined value for TIME */
	downer = TIME;
	while (downer--);
}



/****************************************************************************/
/*									    */
/*       	* * *   S U B R O U T I N E S   * * *                       */
/*									    */
/****************************************************************************/


		/********************************/
		/*		 		*/
		/*  I / O   B U F F E R I N G   */
		/*		 		*/
		/********************************/


		/********************************/
		/*		 		*/
		/*         A B O R T            */
		/*		 		*/
		/********************************/
VOID
abort(a)					/* Print an error message   */
char	*a;					/*   then abort ED	    */
{
	perror(a);
	reboot();
}


		/********************************/
		/*		 		*/
		/*           F E R R            */
		/*		 		*/
		/********************************/
VOID
ferr()						/* Abort when directory full*/
{
	_close(&dfcb);				/* Try to close file so it  */
	abort(dirfull);				/*   can be recovered later */
}



		/********************************/
		/*		 		*/
		/*        S E T P W D           */
		/*		 		*/
		/********************************/

						/* Set password addr (only  */
						/*   CP/M 3 supports this)  */
						/* NOTE: this is a macro    */

#define setpwd()	if (has_bdos3) _setdma(pwd);


		/********************************/
		/*		 		*/
		/*     D E L E T E _ F I L E    */
		/*		 		*/
		/********************************/
VOID
delete_file(fcb)				/* Delete the file describ- */
struct fcbtab	*fcb;				/*   -ed by the arument	    */
{
	setpwd();
	_delete(fcb);
}


		/********************************/
		/*		 		*/
		/*      R E N A M E _ F I L E   */
		/*		 		*/
		/********************************/
VOID
rename_file(fcb)				/* Rename the file describ- */
struct fcbtab	*fcb;				/*   by the argument	    */
{
	delete_file((struct fcbtab *) fcb->resvd);/* *** Delete any existing*/
	setpwd();				/*   file of same name ***  */
	_rename(fcb);				/* Now do rename	    */
}


		/********************************/
		/*		 		*/
		/*      M A K E _ F I L E       */
		/*		 		*/
		/********************************/
VOID
make_file(fcb)					/* Create the file describ- */
struct fcbtab	*fcb;				/*   by the argument	    */
{
	delete_file(fcb);			/* *** Delete any existing  */
	setpwd();				/*  file of same name ***   */	
	dcnt = _create(fcb);			/* Now create file	    */
}


		/********************************/
		/*		 		*/
		/*           F I L L            */
		/*		 		*/
		/********************************/
VOID
fill(s, f, c)					/* Fill string starting at  */
register char	*s, f;				/*   s for c bytes with     */
register int	c;				/*   character s	    */
{
	for (; c--; *s++ = f);
}


/****************************************************************************/
/*									    */
/*	        F I L E   H A N D L I N G   R O U T I N E S                 */
/*	        -------------------------------------------                 */
/*									    */
/****************************************************************************/



		/********************************/
		/*		 		*/
		/*        S E T T Y P E         */
		/*		 		*/
		/********************************/

VOID
settype(fcb, a)					/* Set type of destination  */
struct fcbtab	*fcb;				/*   file afcb to string    */
char	*a;					/*   pointed at by a	    */
{
	move(3, a, fcb->ftype);
}


		/********************************/
		/*		 		*/
		/*        S E T X D M A         */
		/*		 		*/
		/********************************/

						/* Set DMA to transfer buff */
						/* NOTE: this is a macro    */
#define setxdma()	_setdma(xbuff)


		/********************************/
		/*		 		*/
		/*      F I L L S O U R C E     */
		/*		 		*/
		/********************************/
VOID
fillsource()					/* Fill the source buffer   */
{						/*   from file described by */
	register int j;				/*   fcb1 (source file)	    */

	nsource = 0;				/* Point to start of buffer */
	for (j = 0; j <= nbuf; j++)		/* Fill buffer until no more*/
	{					/*   room or until end of   */
		_setdma(&sbuffadr[nsource]);	/*   file reached	    */
		if ((dcnt = _s_read(fcb1)) != 0)
		{
			if (dcnt > 1) ferr();	/* Non-EOF error: report it */
			sbuffadr[nsource] = ENDFILE;
			break;
		}
		else nsource += SECTSIZE;	/* Point to end of new data */
	}
	nsource = 0;				/* Point to buff start again*/
}


		/********************************/
		/*		 		*/
		/*       G E T S O U R C E      */
		/*		 		*/
		/********************************/
int
getsource()					/* Get next character in    */
{						/*   source file	    */
	char	b;

	if (newfile)				/* New file: no next char   */
		return (ENDFILE);
	if (nsource >= bufflength)		/* Char not in buffer: read */
		fillsource();			/*   next bufferful from src*/
	if ((b = sbuffadr[nsource]) != ENDFILE)	/* Not at end of file:	    */
		nsource++;			/*   point to next char	    */
	return (b);				/* Return char (may be	    */
}						/*   ENDFILE)		    */


		/********************************/
		/*		 		*/
		/*      E R A S E _ B A K       */
		/*		 		*/
		/********************************/
BOOLEAN	
erase_bak()					/* Try to free disk space   */
{						/*   by erasing back-up file*/
	if (onefile && newfile)			/* Called when write of	    */
	{					/*   output file fails	    */
		tmpfcb = dfcb;			/* Back-up is open: can't   */
						/*   change its own fcb, so */
		settype(&tmpfcb, backup);	/*   use a copy instead	    */
		delete_file(&tmpfcb);		/* Delete .BAK file	    */
		if (dcnt != 255) return (TRUE); /* TRUE if deletion OK	    */
	}
	return (FALSE);				/* File not deleted	    */
}

		/********************************/
		/*		 		*/
		/*       W R I T E D E S T      */
		/*		 		*/
		/********************************/
VOID
writedest()					/* Write output buffer	    */
{						/* Write only whole sectors:*/
	register int	n, save_ndest;		/*   blocking is done	    */
						/*   elsewhere		    */

	if ((n = ndest / SECTSIZE) == 0) return;/* How many sectors to write*/
						/* Return if none	    */
	save_ndest = ndest;			/* save for error recovery  */
	ndest = 0;
	while (n--)				/* Write out n sectors	    */
	{
	    FOREVER				/* For each sector, write   */
	    {					/*   and move onto next if  */
		_setdma(&dbuffadr[ndest]);	/*   successful.	    */
		if (_s_write(&dfcb) == 0)
		    break;
		if (! erase_bak)		/* If write fails, try to   */
		{				/*   make space & try again */
		    if (ndest != 0)		/* If erase fails, move	    */
		    {				/*   unwritten data to base */
						/*   of buffer.  User must  */
						/*   pick up the pieces	    */
			ndest = save_ndest - ndest;
			move(ndest, &dbuffadr[ndest], dbuffadr);
			longjmp(main_env, DISK_ERR);
		    }
		}
	    }
	    ndest += SECTSIZE;			/* Written OK: bump pointer */
	}
	ndest = 0;				/* Point to base of buffer  */
}


		/********************************/
		/*		 		*/
		/*         P U T D E S T        */
		/*		 		*/
		/********************************/
VOID
putdest(b)					/* Put a character in the   */
char	b;					/*   buffer.  If buffer is  */
{						/*   already full, flush it */
	if (ndest >= bufflength) writedest();	/*   onto disk first	    */
	dbuffadr[ndest++] = b;
}


		/********************************/
		/*		 		*/
		/*        P U T X F E R         */
		/*		 		*/
		/********************************/
VOID
putxfer(c)					/* Put a character into the */
char	c;					/*   transfer buffer.  If   */
{						/*   buffer initially full, */
	if (xbp >= SECTSIZE)			/*   flush it to disk first */
	{
	    FOREVER				/* Write buffer to disk	    */
	    {
		setxdma();
		xfcbext = xfcb.extent;		/* Save current position    */
		xfcbrec = xfcb.record;		/*   for later appends	    */
		if (_s_write(&xfcb) == 0)	/* Leave loop if write OK   */
		    break;
		if (! erase_bak)		/* Not OK: try to make space*/
		    longjmp(main_env, DISK_ERR);/* Erase failure - give up  */
	    }					/* NOTE: xfer file left open*/

	    xbp = 0;				/* Point to buffer base	    */
	}
	xbuff[xbp++] = c;			/* Put character in buffer  */
}


		/********************************/
		/*		 		*/
		/*     C L O S E _ X F E R      */
		/*		 		*/
		/*   (allows LIB files to be	*/
		/*    saved for future edits)	*/
		/*		 		*/
		/********************************/
VOID
close_xfer()					/* Flush transfer buffer to */
{						/*   disk (whether buff full*/
	register int	j;			/*   or not) and close file */
						/* Fill empty part with EOF */
	for (j = xbp; j++ <= SECTSIZE; putxfer(ENDFILE));
	_close(&xfcb);
}


		/********************************/
		/*		 		*/
		/*   C O M P A R E _ X F E R    */
		/*		 		*/
		/********************************/

BOOLEAN 
compare_xfer()					/* Return TRUE if xfcb and  */
{						/*   rfcb are accessing the */
	register int j;				/*   same file		    */
						/* Compare drive, name,	    */
	for (j = 12; j--;)			/*   and type		    */
		if (((char *) &xfcb)[j] != ((char *) &rfcb)[j])
			return (FALSE);
	return (TRUE);
}


		/********************************/
		/*		 		*/
		/*    A P P E N D _ X F E R     */
		/*		 		*/
		/********************************/
VOID
append_xfer()					/* Restore xfer file	    */	
{						/*   to record last written */
	xfcb.extent = xfcbext;			/*   by putxfer; read that  */
	open(&xfcb);				/*   record into xfer buff; */
	xfcb.record = xfcbrec;			/*   leave current record   */
	setxdma();				/*   pointer on that record */
	if (_s_read(&xfcb) == 0)
	{
		xfcb.record = xfcbrec;
		for (xbp = 0; xbp++ < SECTSIZE;)
			if (xbuff[xbp] == ENDFILE) return;
	}					/* On exit xbp points to    */
}						/*   ENDFILE in buffer	    */


/****************************************************************************/
/*									    */
/*	    R O U T I N E S   U S E D   T O   E N D   E D I T		    */  
/*	    -------------------------------------------------		    */	
/*									    */
/****************************************************************************/


		/********************************/
		/*		 		*/
		/*          M O V E U P         */
		/*		 		*/
		/********************************/
VOID
moveup(fcb)					/* Move filename up fcb to  */
struct fcbtab	*fcb;				/*   correct position for   */	
{						/*   use by rename call	    */	
	move(16, (char *) fcb, fcb->resvd);
}


		/********************************/
		/*		 		*/
		/*          F I N I S           */
		/*		 		*/
		/********************************/
VOID
finis()						/* Finish edit, close files,*/
{						/*   rename temp to output  */
	while (ndest % SECTSIZE)		/* Fill to end of current   */
		putdest(ENDFILE);		/*   record with ENDFILEs   */
	writedest();				/* Write the record	    */

	if (!newfile)				/* Close the source file    */
		_close(SFCB);
	_close(&dfcb);				/* Close temp dest file	    */
	if (dcnt == 255)			/* Report error and give up */
		ferr();				/*   on failure		    */
	if (sys)				/* If output is to be system*/
	{					/*   file, set bit to say so*/
		dfcb.ftype[1] |= 0x80;
		setpwd();
		_set_att(&dfcb);
	}
	if (onefile)				/* If source and backup are */
	{					/*   same file, retype src  */
		moveup(SFCB);			/*   to .BAK (this is case  */	
						/*   if no o/p file given)  */	
		settype((struct fcbtab *) SFCB->resvd, backup);
		rename_file(SFCB);
	}

	moveup(&dfcb);				/* Retype dest file to same */
						/*   type as source	    */	
	settype((struct fcbtab *) dfcb.resvd, dtype);
	rename_file(&dfcb);
}



/****************************************************************************/
/*									    */
/*	            C O M M A N D   R O U T I N E S                         */
/*	            -------------------------------                         */
/*									    */
/****************************************************************************/



		/********************************/
		/*		 		*/
		/*        P R T N M A C         */
		/*		 		*/
		/********************************/

						/* Print character if not   */
						/*   part of macro expansion*/
						/* NOTE: this is a macro    */
#define	prtnmac(ch)	if (! mp) printc(ch);


		/********************************/
		/*		 		*/
		/*       L O W E R C A S E      */
		/*		 		*/
		/********************************/

						/* Return TRUE if character */
						/*   is lowercase	    */
						/* NOTE: this is a macro    */
#define lowercase(ch)	((LCA <= ch) && (ch <= LCZ))


		/********************************/
		/*		 		*/
		/*          U C A S E           */
		/*		 		*/
		/********************************/
int
ucase(ch)					/* Translate character to   */
{						/*   upper case if it is a  */
	return (lowercase(ch) ? ch & 0x5f : ch);/*   lower case letter	    */
}



		/********************************/
		/*		 		*/
		/*       G E T P A S S W D      */
		/*		 		*/
		/********************************/

getpasswd()					/* Get password from user  */
{
	char	c;
	register int	j;

	crlf();					/* Ask for password	    */
	print("Password? $");
	FOREVER					/* Until password obtained  */
	{
retry:
	    fill(pwd, ' ', PASSLEN);		/* Fill password with spaces*/
	    for (j = 0; j < PASSLEN;)		/* Read PASSLEN characters  */
	    {					/*   without echoing	    */	
						/* What did user enter?	    */
		switch (c = ucase((int) _conio(0xff)))
		{
		  case CR:			/* All done		    */
		    goto way_out;
		  case CTL_X:			/* Start over		    */
		    goto retry;
		  case CTL_H:
		    if (j > 0)
		    	pwd[--j] = ' ';		/* Prepare to reenter	    */
		    break;			/*   previous character	    */
		  case CTL_C:
		    reboot();			/* Exit from ED at once	    */
		  default:
		    if (c >= ' ')		/* Printable?  Put in passwd*/
			pwd[j++] = c;
		}
	    }
	}
way_out:
	c = (char) break_key(); 		/* Clear raw I/O mode	    */
}


		/********************************/
		/*		 		*/
		/*         U T R A N		*/
		/*		 		*/
		/********************************/
int
utran(c)					/* Translate to upper case  */
int	c;					/*   if translate flag set  */
{						/*   (for U/C only terminal)*/
	if (c == ESC) return (ENDFILE);		/* Translate ESC to ENDFILE */
	return ((translate) ? ucase(c) : c);
}


		/********************************/
		/*		 		*/
		/*     P R I N T V A L U E      */
		/*		 		*/
		/********************************/
VOID
printvalue(v)					/* Print the line number as */
UWORD	v;					/*   unsigned decimal value */
{
	register BOOLEAN	zero;
	register int	d, k;

	zero = FALSE;				/* Blank leading zeros	    */
	for (k = 10000; k != 0; k /= 10)	/* Print 5 digits	    */	
	{
		d = v / k;
		v %= k;
		if ((zero) || (d != 0))		/* Print first non-zero	    */
		{				/*   and all following	    */	
			zero = TRUE;		/*   digits		    */	
			printc('0' + d);
		}
		else printc(' ');
	}
}


		/********************************/
		/*		 		*/
		/*      P R I N T L I N E       */
		/*		 		*/
		/********************************/
VOID
printline(v)					/* Print line number:	    */
UWORD	v;					/*   "12345:  " if inserting*/
{						/*   else "12345: *"	    */
	if (! lineset) return;			/* Do nothing if no number  */
	printvalue(v);				/*   wanted		    */
	printc(':');
	printc(' ');
	printc((inserting) ? ' ' : '*');
}


		/********************************/
		/*		 		*/
		/*     P R I N T B A S E        */
		/*		 		*/
		/********************************/

						/* Print current line number*/
						/*   (baseline)		    */
						/* NOTE: this is a macro    */
#define printbase()	printline(baseline)


		/********************************/
		/*		 		*/
		/*   P R I N T N M B A S E      */
		/*		 		*/
		/********************************/

						/* Print current line number*/
						/*   (baseline) if not	    */							/*   expanding macro	    */
						/* NOTE: this is a macro    */
#define printnmbase()	if (! mp) printline(baseline)


		/********************************/
		/*		 		*/
		/*          G E T C M D         */
		/*		 		*/
		/********************************/

						/* Get next char from cmd   */
						/*   buffer or CR if none   */
						/* NOTE: this is a macro    */
#define getcmd()	((buff[ncmd + 1] != 0) ? buff[++ncmd] : CR)


		/********************************/
		/*		 		*/
		/*           R E A D C          */
		/*		 		*/
		/********************************/
int
readc()						/* Read next input character*/
{						/* This may be from	    */
						/*   a) macro expansion	    */
						/*   b) keyboard in insert  */
						/*   c) command line	    */
	if (mp != 0)				/* From macro expansion?    */
	{
	    if (break_key())			/* CTL_Y stops macro	    */
		longjmp(main_env, OVERCOUNT);
	    if (xp >= mp)			/* Reached end of macro?    */
	    {
		if ((mt != 0) && (--mt == 0))	/* Count down repeats	    */
		    longjmp(main_env, OVERCOUNT);/*Macro done		    */
	    xp = 0;				/* Point to macro start	    */
	    }
	    return (utran(macro[xp++]));	/* Return char from macro   */
	}

	if (inserting)				/* Inserting? Just read	    */
		return (utran((int) _conio(0xff)));	/* NOTE: no echo    */

	if (readbuff)				/* Take from command buffer */
	{					/* Get new line if empty    */
	    readbuff = FALSE;
	    if (lineset && (column == 0))	/* Need a line number?	    */
	    {
		if (back >= maxm)		/* Does a number make sense?*/
		    printline(0);		/* No: print spaces	    */
		else
		    printbase();
	    }
	    else
		printc('*');			/* No number: just print "*"*/
	    readcom();				/* Read a line		    */	
	    combuf.cbp = 0;			/* Point at first char	    */
	    printc(LF);
	    column = 0;
	}
						/* Buffer loaded: get char  */
	if (readbuff = (combuf.cbp == combuf.comlen))
	    combuf.comline[combuf.cbp] = CR;	/* If last, return CR;	    */
						/*   reload buffer next time*/
	return (utran(combuf.comline[combuf.cbp++]));
}


		/********************************/
		/*		 		*/
		/*          G E T _ U C         */
		/*		 		*/
		/********************************/
VOID
get_uc()					/* Get upper case char from */
{						/*   buffer or command line */
	chr = ucase((tail) ? getcmd() : readc());
}


		/********************************/
		/*		 		*/
		/*           D E L I M          */
		/*		 		*/
		/********************************/

BOOLEAN 
delim()						/* TRUE if chr is delimiter */
{						/* Complains about wildcards*/
	static char del[] =			/* Table of delimiters	    */
		{
		CR, ENDFILE,' ',',','.',';','=',':','!','=','[',']','*','?'
	    /*   0    1      2   3   4   5   6   7   8   9  10  11  12  13*/
		};

	for (delimiter = 0; delimiter < sizeof del; delimiter++)
	{
		if (chr == del[delimiter])
		{
			if (delimiter > 11) 	/* * or ?		    */
				perror("cannot edit wildcard filename$");
			return (TRUE);
		}
	}
	return (FALSE);
}



		/********************************/
		/*		 		*/
		/*      P A R S E _ F C B       */
		/*		 		*/
		/********************************/
BOOLEAN 
parse_fcb(fcbadr)				/* Parse input file name;   */
struct fcbtab	*fcbadr;			/*   set up in fcb if valid */
{						/*   return FALSE if invalid*/
	register int	j;			/* There must be an easier  */
	BOOLEAN		pflag, colon_found;	/*   way to do this...	    */

	pflag = FALSE;				/* No non-delimiters found  */
	flag = TRUE;				/* Filename is valid so far */
	dot_found = colon_found = FALSE;	/* No drive, type found yet */
	get_uc();
	if ((chr == CR) || (chr == ENDFILE))	/* Empty line or end of file*/
	    return (FALSE);			/*    does not change fcb   */

	fill((char *) fcbadr->fname, ' ', 11);	/* File name to spaces	    */
	fill((char *) &fcbadr->extent, 0, 21);   /* Rest of fcb to nulls    */
	while (chr == ' ')			/* Skip leading spaces	    */	
	    get_uc();
						/* Parse the filename	    */
	fcbadr->drive = j = 0;			/* Guess it's default drive */
	
	FOREVER					/* Find drive and/or name   */
	{
	    while (! delim())
	    {
		if (j >= 8) goto err;		/* Too long for filename    */
		fcbadr->fname[j++] = chr;	/* Put into fcb		    */
		pflag = TRUE;
		get_uc();
	    }
	    if (chr != ':')			/* Was this a drive name?   */
		break;				/* No.  Must be file name   */

	    if ((j != 1)			/* Drive not 1 char: invalid*/
	       || colon_found		/* Second colon: error	    */
	       || ((fcbadr->drive = fcbadr->fname[0] - 'A' + 1) > 16))
		goto err;			/* drive > P : invalid	    */	

	    fcbadr->fname[0] = ' ';		/* Leave fname blank	    */
	    colon_found = TRUE;		/* Go see if there is a name*/
	    j = 0;
	    get_uc();
	}

	if (chr == '.')				/* Start of extension	    */	
	{
	    j = 0;
	    dot_found = TRUE;
	    get_uc();
	    while (! delim())
	    {
	        if (j >= 3) goto err;		/* Extension too long	    */
		fcbadr->ftype[j++] = chr;
		pflag = TRUE;
		get_uc();
	    }
	}
	if (chr == ';')				/* Start of password	    */
	{
	    fill(fcbadr->resvd,' ', 8);		/* Initialize to spaces	    */
	    j = 0;
	    get_uc();
	    while (! delim())
	    {
		if (j >= 8)
		    goto err;			/* Password too long	    */	
		fcbadr->resvd[j++] = chr;
		pflag = TRUE;
		get_uc();
	    }
	    move(8, fcbadr->resvd, pwd);	/* Make copy for ED	    */
	}

	if ((delimiter <= 3) && pflag)		/* Everything OK?	    */
	    return (pflag);
						/* Not CR, ENDFILE, space   */
						/*   or comma: error	    */
err:
	perror(invalid);			/* Complain about invalid   */
	return (flag = FALSE);			/*   filename		    */
}



		/********************************/
		/*		 		*/
		/*        C O P Y D E S T       */
		/*		 		*/
		/********************************/

						/* Copy source name to	    */
						/*   destination	    */
						/* NOTE: this is a macro    */

#define copydest()	move(16, (char *) fcb1, (char *) &dfcb)



		/********************************/
		/*		 		*/
		/*        S E T D E S T         */
		/*		 		*/
		/********************************/
VOID
setdest()					/* Set up the destination   */
{						/*   file		    */
	register int	j, k;			/* Onefile == TRUE on entry */

	k = SFCB->drive;			/* Get source drive	    */
	if (! tail)				/* No command line params:  */
	{					/*   get file name from user*/
		print("Output file? $");
		readcom();
		combuf.cbp = readbuff = 0;
		printc(LF);
	}
	if (parse_fcb(&dfcb)			/* If name is valid and    */
	   && (dfcb.fname[0] != ' '))		/*   non-blank, is it same */
	{					/*   as source name?	   */	
		k = dfcb.drive;			/* Save dest drive name	   */
		for (j = 0; j < 11; j++)
			if (! (onefile = (fcb1->fname[j] == dfcb.fname[j])))
			 	break;
	}
	if (onefile) 				/* Same name?  Copy it.	   */
	{
		copydest();		
		dfcb.drive = k;			/* Plug in drive name	   */
	}
	move(3, dfcb.ftype, dtype);		/* save destination type   */
}



		/********************************/
		/*		 		*/
		/*       S E T R D M A          */
		/*		 		*/
		/********************************/

						/* Set DMA address to read  */
						/*   library file	    */
						/* NOTE: this is a macro    */

#define setrdma()	_setdma(buff)


		/********************************/
		/*		 		*/
		/*        R E A D F I L E       */
		/*		 		*/
		/********************************/
int
readfile()					/* Read a character from a  */
{						/*   library file	    */
	if (rbp >= SECTSIZE)			/* Refill buffer if end has */
	{					/*   been reached	    */	
		setrdma();
		if (_s_read(&rfcb) != 0)
			return (ENDFILE);	/* End of libray file	    */
		rbp = 0;			/* Point to buffer start    */	
	}
	return (utran(buff[rbp++]));		/* Return current character */
}


		/********************************/
		/*		 		*/
		/*     W R I T E _ X F E R      */
		/*		 		*/
		/********************************/
VOID
wrt_xfer()					/* Write lines to transfer  */
{						/*   file		    */
	register int	j;

	xbp = 0;				/* Initialize transfer index*/
	if (xferon && compare_xfer())		/* If transfer file in use  */
		append_xfer();			/*   & same as read file,   */
						/*   prepare to append to it*/
	else
	{					/* Otherwise, create it:    */
		xferon = TRUE;			/* Name is that of read file*/
		move(12, (char *) &rfcb, (char *) &xfcb);
		xfcbext = xfcb.extent = 0;
		xfcbrec = xfcb.record = 0;
		make_file(&xfcb);
		if (dcnt == 0xff)		/* Can't create: give up    */
			longjmp(main_env, DIR_ERR);
	}

	setlimits();				/* Map line cnt to pointers */
	for (j = first; j <= lastc; putxfer(base[j++]));/* Write out to file*/
}



/****************************************************************************/
/*									    */
/*	                I N I T I A L I Z A T I O N                         */
/*	                ---------------------------                         */
/*									    */
/****************************************************************************/



		/********************************/
		/*		 		*/
		/*          S E T U P           */
		/*		 		*/
		/********************************/
VOID
setup()						/* Start up edit session    */
{
	int	error_code;

	SFCB->extent =  SFCB->s2 = 0;
	SFCB->record = 0;			/* Open the source file    */	
	if (has_bdos3)
	{
		_ret_errors(0xfe);	        /* Set error mode	    */
		setpwd();
	}
	error_code = _open(SFCB);		/* Get source file name	    */
	if (has_bdos3)				/* Has extended BDOS errors */
	{
		_ret_errors(0);			/* Reset error mode	    */
		if (error_code == 0x7ff)	/* Password required?	    */
		{
			getpasswd();		/* Let them enter pwd	    */
			crlf();
			crlf();
			setpwd();		/* Set DMA to pwd	    */
						/* Retry the open	    */
			error_code = _open(fcb1);    
		}
		if (((error_code & 0xff) == 0xff)/* Abort unless open	    */
		   && ((error_code >> 8) != 0))	/*   successful or file not */
			abort(notavail);	/*   found		    */	
	}
	dcnt = error_code & 0xff;
	if (onefile)				/* If output same as input  */	
	{
		if (fcb1->ftype[0] & 0x80)	/* Check for read only	    */	
			abort("File is read/only$");
		if (fcb1->ftype[1] & 0x80) 	/*   and system attribute   */
		{
			if (fcb1->fname[7] & 0x80)
				dcnt = 255;	/* User 0 file so create    */
			else
				sys = TRUE;
		}
	}

	if (dcnt == 255)			/* A new file is needed	    */	
	{
		if (! onefile)			/* Two files given, but	    */
			abort(not_found);	/*   can't open source	    */
		newfile = TRUE;			/* Onefile: must be new	    */
		print("new file$");
		crlf();
	}

	settype(&dfcb, tempfl);			/* Make a new .$$$ temp file*/
	dfcb.extent = 0;
	make_file(&dfcb);
	if (dcnt == 255) ferr();		/* Couldn't do it: error    */

	if (protection != 0)			/* Create password if	    */	
	{					/*   necessary		    */
		dfcb.extent = protection | 1;	/* Set password		    */	
		setpwd();
		write_xfcb(&dfcb);
	}
	dfcb.record = 0;			/* Next record is zero	    */
	dfcb.extent = 0;			/*   in file extent zero    */

	nsource = bufflength;			/* Initialize edit buffer   */
	ndest = 0;
	baseline = 1;				/* Start with line 1	    */
}

/****************************************************************************/
/*									    */
/*	       E D I T   B U F F E R   M A N A G E M E N T                  */
/*	       -------------------------------------------                  */
/*									    */
/*	Some words about how ED manages its edit buffer.  This is best	    */
/*	understood by remembering the original ED documentation ("ED: a	    */
/*	Context Editor for the CP/M Disk System", DRI 1976, 1978.)  This    */
/*	shows the following conceptual picture of the memory buffer (the    */
/*	text shown is arbitrary):					    */
/*									    */
/*	First line	ABCDEFGHIJKLMNOP<CR><LF>			    */
/*									    */
/*	Second line	QRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234<CR><LF>    */
/*									    */
/*	Current line	567890ABCDEFGHIJKLMNOPRSTUVWXYZabcdef<CR><LF>	    */
/*						      ^			    */
/*					      Current | Pointer		    */
/*									    */
/*	Last line	ghijklmnopqrstuvwxyz<CR><LF>			    */
/*									    */
/*	The buffer seems to the user like a contiguous block of text	    */
/*	within which the Current Pointer can be moved at will, either	    */
/*	explicitly (for example, with the L command) or as a side effect    */
/*	of a command (X deletes the character on which the pointer rests,   */
/*	in effect moving the pointer to the next character.)		    */
/*									    */
/*	Although the text seems to the user to be stored contiguously, in   */
/*	fact it is not: it is stored as two separate blocks.  Text from	    */
/*	the first character on the first line up to BUT NOT INCLUDING the   */
/*	character on which the Current Pointer rests is held as a contiguous*/
/*	block at the base of the edit buffer.  The remaining text,	    */
/*	including the character on which the pointer rests, is held as a    */
/*	contiguous block at the top of the buffer.  If the Current Pointer  */
/*	is moved forward (towards the end of the text) characters are moved */
/*	from the beginning of the block at the top of the buffer to the     */
/*	end of the block at the bottom of the buffer until the first	    */
/*	character in the top block is the one on which the pointer rests.   */
/*	Similarly, if the pointer is moved backward, characters are moved   */
/*	from the end of the block at the bottom of the buffer to the	    */
/*	beginning of the block at the top of the buffer.		    */
/*									    */
/*	The data movement is accomplished by mover().  Deletion of 	    */
/*	characters before or after the current pointer does not require     */
/*	any data movement.  Instead an adjustment of the pointers to the    */
/*	end of the bottom block or to the beginning of the top block is	    */
/*	accomplished by set_ptrs().					    */
/*									    */
/*	Many of ED's operations act over a given number of lines rather	    */
/*	than of characters.  This is accomplished by having setimits()	    */
/*	convert the line count to a character count before the primitive    */
/*	operation takes place.  Mover() and set_ptrs() keep track of	    */
/*	current line number by counting line-feeds as they are encountered. */
/*	Note that deleting data after the Current Pointer does not affect   */
/*	the current line number.					    */

/*	Most ED operations affect either data before the Current Pointer    */
/*	(direction backward), on or after it (direction forward) but not    */
/*	both.  The two operations which operate somewhere else - Append	    */
/*	and Write - temporarily compact the data to a single contiguous	    */
/*	block at the bottom or top of the buffer respectively.  When	    */
/*	complete, they split the data in two again.			    */
/*									    */
/*	You've read the book.  Now see the picture.  The Current pointer is */
/*	resting on the second 'Z' in the buffer.  Dots represent the free   */
/*	area:								    */
/*									    */
/*	+-------------------------------+  <--	base points here	    */
/*	|<LF>ABCDEFGHIJKLMNOP<CR><LF>QRS|				    */
/*	|TUVWXYZabcdefghijklmnopqrstuvwx|				    */
/*	|yz1234<CR><LF>567890ABCDEFGHIKL|				    */
/*	|MNOPQRSTUVWXY..................|  <--  base[front] is first char   */
/*	|...............................|	in free area (ie first dot) */
/*									    */
/*	|...............................|				    */
/*	|...............Zabcdef<CR><LF>g|  <--	base[back] is current char  */
/*	|hijklmnopqrstuvwxyz<CR><LF><\0>|				    */
/*	+-------------------------------+				    */
/*									    */
/*	The leading line feed and terminating null are put into the buffer  */
/*	by ED.  They are not part of the user's text.			    */
/*									    */
/****************************************************************************/



		/********************************/
		/*		 		*/
		/*      D I S T N Z E R O       */
		/*		 		*/
		/********************************/
BOOLEAN 
distnzero()					/* If non-zero distance	    */
{						/*   decrement distance and */
	if ( distance != 0)			/*   return TRUE	    */	
	{
		distance--;
		return (TRUE);
	}
	return (FALSE);				/* distance == 0	    */	
}



		/********************************/
		/*		 		*/
		/*      S E T L I M I T S       */
		/*		 		*/
		/********************************/
VOID
setlimits()					/* Set memory limits over   */
{						/*   which command operates */
	register int j, k, limit, m;		/*   using distance and	    */
	BOOLEAN middle, looping;		/*   direction		    */	

	relline = 1;				/* Relative line count	    */
	if (direction == BACKWARD)		/* Working down buffer?	    */	
	{
		distance++;			/* Account for current line */
		j = front;
		limit = 0;
		k = 0xffff;
	}
	else
	{
		j = back;
		limit = maxm;
		k = 1;
	}
	looping = TRUE;				/* The algorithm scans the  */
	while (looping)				/*   buffer until it has    */
	{					/*   found required number  */
		while ((middle = (j != limit))	/*   of line feeds or until */
		      && (base[m = j + k] != LF))/*  buffer limits reached  */
			j = m;			/* If operation covers	    */
		looping = (--distance != 0);	/*   lines outside buffer,  */
		if (! middle)			/*   distance is non-zero   */
		{				/*   on exit		    */
			looping = FALSE;
			j -= k;
		}
		else
		{
			relline--;
			if (looping)
				j = m;
		}
	}

	if (direction == BACKWARD)		/* Operation starts at	    */
	{					/*   base[j] and stops at   */
		first = j;			/*   base[front - 1]	    */
		lastc = front - 1;
	}
	else					/* FORWARD: operation	    */
	{					/*   at base[back  -1] and  */
		first = back + 1;		/*   staps at base[j + 1]   */
		lastc = j + 1;
	}
}


		/********************************/
		/*		 		*/
		/*       D E C F R O N T        */
		/*		 		*/
		/********************************/

						/* Decrement front pointer, */
						/*   and, if now at line    */
						/*   new line, baseline	    */
						/* NOTE: this is a macro    */

#define decfront() if (base[--front] == LF) baseline--



		/********************************/
		/*		 		*/
		/*       M E M _ M O V E        */
		/*		 		*/
		/********************************/
VOID
mem_move(moveflag)				/* If moveflag is TRUE, move*/
BOOLEAN	moveflag;				/*   character at base[back]*/
{						/*   to base[front]; adjust */
	register UWORD	tfront, tback;		/*   pointers and baseline  */
	register char	*tbase;			/* Use registers for speed  */

	tfront = front;				/* Get current values	    */
	tback = back;
	tbase = base;

	if (direction == FORWARD)		/* If moveflag FALSE, just  */
	{					/*   adjust pointers (lose  */
	    while (tback < lastc)		/*   the character)	    */	
	    {
		tback++;
		if (moveflag)
		{				/* Count lines if real move */
		    if ((tbase[tfront++] = base[tback]) == LF)
			baseline++;
		}
	    }
	}
	else					/* Backward move moves in   */
	{					/*   opposite direction;    */	
	    while (tfront > first)		/*   adjusts baseline even  */
	    {					/*   if no move made	    */
		if (tbase[--tfront] == LF)
		    baseline--;
		if (moveflag)
		    tbase[tback--] = tbase[tfront];
	    }
	}

	front = tfront;				/* Assign final values to   */
	back = tback;				/*   globals		    */
}



		/********************************/
		/*		 		*/
		/*         M O V E R            */
		/*		 		*/
		/********************************/

						/* Force a memory move	    */	
						/* NOTE: this is a macro    */

#define mover() mem_move(TRUE)



		/********************************/
		/*		 		*/
		/*         S E T P T R S        */
		/*		 		*/
		/********************************/

						/* Reset pointers, deleting */
						/*   characters (used by    */							/*   delete command)	    */
						/* NOTE: this is a macro    */

#define setptrs() mem_move(FALSE)



		/********************************/
		/*		 		*/
		/*      M O V E L I N E S       */
		/*		 		*/
		/********************************/

						/* Set memory limits and    */							/*   force a move	    */
						/* NOTE: this is a macro    */

#define movelines()	{setlimits(); mover();}



		/********************************/
		/*		 		*/
		/*       S E T F R O N T        */
		/*		 		*/
		/********************************/
VOID
setfront(newfront)				/* Adjust front to lower    */
UWORD	newfront;				/*   value, deleting chars  */
{						/*   and adjusting baseline */
	while (front != newfront)		/*   (used by S & J cmds)   */
		decfront();
}



		/********************************/
		/*		 		*/
		/*     S E T C L I M I T S      */
		/*		 		*/
		/********************************/
VOID
setclimits()					/* Set memory move limits   */
{
	if (direction == BACKWARD)
	{
		lastc = back;
		first = (distance > front) ? 1 : front - distance;
	}
	else
	{
		first = front;
		lastc = (distance >= max - back) ? maxm : back + distance;
	}
}


		/********************************/
		/*		 		*/
		/*        R E A D L I N E       */
		/*		 		*/
		/********************************/
VOID
readline()					/* Read line of user input  */
{						/*   into end of buffer	    */
	register char	b;

	FOREVER
	{
		if (front >= back)		/* Buffer overflow: give up */
			longjmp(main_env, OVERFLOW);
		if ((b = getsource()) == ENDFILE)
		{				/* End of user input	    */
			distance = 0;
			break;			/* Translate to upper case  */	
		}				/*   if necessary before    */
		base[front++] = (upper) ? utran(b) : b;	/* adding to buffer */
		if (b == LF)			/* Bump current line ?	    */
		{
			baseline++;
			break;
		}
	}
}




		/********************************/
		/*		 		*/
		/*      W R I T E L I N E       */
		/*		 		*/
		/********************************/
VOID
writeline()					/* Write a single line out */
{						/*   on the disk	    */
	register char	b;

	FOREVER
	{
		if (back >= maxm)		/* Nothing to write?	    */
		{
			distance = 0;		/* Command is done	    */
			break;
		}
		putdest(b = base[++back]);	/* Write a character	    */	
		if (b == LF)			/* Tally lines written	    */
		{
			baseline++;
			break;
		}
	}
}




		/********************************/
		/*		 		*/
		/*         W R H A L F          */
		/*		 		*/
		/********************************/
VOID
wrhalf()					/* Write lines until the    */
{						/*   buffer is at least	    */	
	distance = 0xffff;			/*   half empty	(free up    */
	while (distnzero())			/*   some workspace)	    */
	{
		if (hmax >= (maxm - back))
			distance = 0;
		else
			writeline();
	}
}



		/********************************/
		/*		 		*/
		/*      W R I T E O U T         */
		/*		 		*/
		/********************************/
VOID
writeout()					/* Write number of lines    */
{						/*   given by distance	    */
	direction = BACKWARD;			/* Called by W and E cmds   */
	first = 1;
	lastc = back;
	mover();
	if (distance == 0)
		wrhalf();
	while (distnzero())
		writeline();
	if (back < lastc)
	{
		direction = FORWARD;
		mover();
	}
}




		/********************************/
		/*		 		*/
		/*        C L E A R M E M       */
		/*		 		*/
		/********************************/

						/* Write out the whole	    */
						/*   memory buffer	    */
						/* NOTE: this is a macro    */

#define clearmem()	{distance = 0xffff; writeout();}




		/********************************/
		/*		 		*/
		/*      T E R M I N A T E       */
		/*		 		*/
		/********************************/
VOID
terminate()					/* Clear buffers before	    */
{						/*   terminating edit	    */
	clearmem();
	if (! newfile)
		while ((chr = getsource()) != ENDFILE)
			putdest(chr);
	finis();
}



/****************************************************************************/
/*									    */
/*	          C O M M A N D   P R I M I T I V E S                       */
/*	          -----------------------------------                       */
/*									    */
/****************************************************************************/




		/********************************/
		/*		 		*/
		/*        I N S E R T           */
		/*		 		*/
		/********************************/
VOID
insert()					/* Insert a character into  */
{						/*   memory buffer	    */
	if (front == back)
		longjmp(main_env, OVERFLOW);
	if ((base[front++] = chr) == LF)	/* Tally new lines	    */
		baseline++;
}



		/********************************/
		/*		 		*/
		/*       S C A N N I N G        */
		/*		 		*/
		/********************************/
BOOLEAN	
scanning()					/* Read a character.  Return*/
{						/*   TRUE if not ENDFILE and*/
	return (((chr = readc()) != ENDFILE)	/*   not CR.  When inserting*/
	       && ((chr != CR) || inserting));	/*   CR also returns TRUE   */
}


		/********************************/
		/*		 		*/
		/*        C O L L E C T         */
		/*		 		*/
		/********************************/
VOID
collect()					/* Read characters from	    */
{						/*   command buffer into    */
	while (scanning())			/*   scratch buffer for Next*/
	{					/*   Juxt or Substitute cmds*/
		if (chr == CTL_L)		/* Translate CTL_L to CR LF */
		{
			scratch[wbe] = chr = CR;/* Complain if scratch buff */
			if (++wbe>= SCRSIZE)	/*   overflows		    */
				longjmp(main_env, OVERFLOW);
			chr = LF;
		}
		if (chr == 0)
			longjmp(main_env, BADCOM);
		scratch[wbe] = chr;
		if (++wbe >= SCRSIZE)
			longjmp(main_env, OVERFLOW);
	}					/* wbe indexes next free    */
}						/*   byte on exit	    */



		/********************************/
		/*		 		*/
		/*           F I N D            */
		/*		 		*/
		/********************************/
BOOLEAN 
find(p1, p2)					/* On entry, p1 indexes the */
UWORD	p1, p2;					/*   start of a string in   */
{						/*   the scratch buffer, p2 */
	register UWORD	j, k;			/*   indexes the end.	    */
	BOOLEAN		match;			/* Return TRUE if a matching*/
						/*   string is found between*/
	j = back ;				/*   base[back + 1] and	    */
	match = FALSE;				/*   base[maxm - 1]	    */
						/* If match found, move	    */
						/*   memory so that matched */
						/*   string is at base[back]*/

	while (! match && (maxm > j))		/* Try  match at base[j + 1]*/
	{
		lastc = ++j;
		k = p1;				/* Index 1st match char	    */
		while (! (match = (k == p2))
		      && (scratch[k] == base[lastc]))
		{				/* On exit, match is TRUE if*/
			k++;			/*   complete match found   */
			lastc++;
		}
	}
	if (match)				/* Success: do move	    */
	{
		lastc--;
		mover();
	}
	return (match);
}



		/********************************/
		/*		 		*/
		/*         S E T F I N D        */
		/*		 		*/
		/********************************/
VOID
setfind()					/* Set up search string for */
{						/*   F, N & S commands	    */
	wbe = 0;				/* Index buffer start	    */
	collect();
	wbp = wbe;				/* Index buffer end	    */
}


		/********************************/
		/*		 		*/
		/*       C H K F O U N D	*/
		/*		 		*/
		/********************************/
VOID
chkfound()					/* Flag error if string not */
{						/*   found in F & S commands*/
	if (! find(0, wbp))
		longjmp(main_env, OVERCOUNT);
}



		/********************************/
		/*		 		*/
		/*      P A R S E _ L I B       */
		/*		 		*/
		/********************************/
BOOLEAN 
parse_lib(fcbadr)
struct fcbtab	*fcbadr;			/* Parse a filename string  */
{						/* (read, xfer or lib file) */
	BOOLEAN b;				/* Fill in any blank fields */

	b = parse_fcb(fcbadr);			/* Try the parse	    */
	if (! flag)				/* If it failed, give up    */
	{
		flag = 'O';
		longjmp(main_env, RESET);
	}
	if ((fcbadr->ftype[0] == ' ')		/* Parse worked.  If no	    */
	   && (! dot_found))			/*   extension, use default */
		move(3, libfcb.ftype, fcbadr->ftype);
	if (fcbadr->fname[0] == ' ')		/* If no name, use default  */
		move(8, libfcb.fname, fcbadr->fname);
	return (b);
}



		/********************************/
		/*		 		*/
		/*      T Y P E L I N E S       */
		/*		 		*/
		/********************************/

typelines()					/* Type out lines (T and    */
{						/*   many other commands)   */	
	register int	j;
	int	c;

	setlimits();				/* Map count to buffer ptrs */
	inserting = TRUE;			/* Disable the * prompt	    */
	if (direction == FORWARD)		/* Do we need new line	    */
	{					/*   before we start?	    */
	    relline = 0;
	    j = front;
	}
	else
	    j = first;

	if ((c = base[--j]) == LF)
	{
	    if (column != 0)
		crlf();
	}
	else
	    relline++;

	for (j = first; j <= lastc; j++)	/* Print loop		    */
	{
	    if (c == LF)			/* New line?		    */
	    {
		printline(baseline + relline++);/* Print line number	    */
		if (break_key())		/*   and bump count	    */
		    longjmp(main_env, OVERCOUNT);	/* User wants out   */
	    }
	    printc(c = base[j]);		/* Print the character	    */
	}
}



		/********************************/
		/*		 		*/
		/*            P A G E           */
		/*		 		*/
		/********************************/
VOID
page()						/* Page command: move N	    */
{						/*   pages and print	    */
	UWORD	tdist;				/* place to save distance   */
	int	j;

	tdist = distance;
	distance = lpp;				/* Move one page in current */
	movelines();				/*   direction		    */
	j = direction;				/* Print page in forward    */
	direction = FORWARD;			/*   direction		    */
	distance = lpp;
	typelines();
	direction = j;				/* Restore direction	    */	
	distance = ((lastc == maxm) || (first == 1)) ? 0 : tdist;
}



		/********************************/
		/*		 		*/
		/*           W A I T            */
		/*		 		*/
		/********************************/
VOID
wait()						/* Half-second wait (Z cmd) */
{
	register int	j;

	for (j=0; j++ <= 19; time())
		if (break_key())		/* Allow user to abort	    */
			longjmp(main_env, RESET);
}



		/********************************/
		/*		 		*/
		/*         A P P H A L F        */
		/*		 		*/
		/********************************/
VOID
apphalf()					/* Append until buffer is   */
{						/*   at least half full	    */
	distance = 0xffff;			/* Set maximum distance	    */	
	while (distnzero())			/* Until buffer half full   */
	{
		if (front >= hmax) distance = 0;
		else  readline();		/*   read lines		    */
	}
}



		/********************************/
		/*		 		*/
		/*       I N S C R L F          */
		/*		 		*/
		/********************************/
VOID
inscrlf()					/* Insert CR LF characters  */
{
	chr = CR;
	insert();
	chr = LF;
	insert();
}



		/********************************/
		/*		 		*/
		/*  I N S _ E R R O R _ C H K	*/
		/*		 		*/
		/********************************/

						/* Test invalid delete or   */
						/*   backspace at beginning */
						/*   of insert		    */
						/* NOTE: this is a macro    */

#define ins_error_chk()	if ((tcolumn == 255) || (front == 1))\
				longjmp(main_env, RESET);


		/********************************/
		/*		 		*/
		/*   I N S E R T _ C H A R S    */
		/*		 		*/
		/********************************/
VOID
insert_chars()					/* Insert characters into   */
{						/*   buffer (I command)	    */
	int	tcolumn, qcolumn;		/* temps during backspace    */

	if (inserting = (combuf.cbp == combuf.comlen)
			&& (mp == 0))
	{					/* I<CR>? Enter line insert */
	    tcolumn = 255;			/* Stop backspace working   */
	    distance = 0;			/*   temporarily	    */
	    direction = BACKWARD;
	    if (base[front - 1] == LF)		/* Start of new line? If so */
		printbase();			/*   print number	    */
	    else
		typelines();			/* No.  Print end of current*/
	}

	while (scanning())			/* Do until end of insert   */
	{					/*   reached		    */
	    switch (chr)			/* Test all special cases   */
	    {
	      case CTL_H:			/* Backspace, screen style  */
	        ins_error_chk();		/* Abort if leftmost column */
	        tcolumn = column;		/* What is the current posn */
	        decfront();			/* Delete char in buffer    */
	        if (tcolumn > scolumn)		/* Have there been printable*/
	        {				/*   characters on this line*/
		    printsuppress = TRUE;	/* Yes: Make change on scren*/
		    column = scolumn;		/* Recompute current pos'n  */
		    typelines();		/*   (covers case where TAB */
		    printsuppress = FALSE;	/*    char is deleted)	    */
						/* Move to greater of column*/
						/*   and start column	    */
		    qcolumn = (scolumn > column) ? scolumn : column;
		    column = tcolumn;		/* Restore original value   */
		    while (column > qcolumn)
			backspace();		/* Erase char(s) on screen  */
	        }
	        else				/* Cover case where newline */
	        {				/*   sequence deleted	    */
		    if (base[front - 1] == CR)
			decfront();
		    crlf();
		    printnmbase();
		    typelines();		/* Print previous line	    */
	        }
	        break;

	      case LF:				/* Map line feeds into CR LF*/
	      case CR:				/* Sme for carriage return  */
		prtnmac(CR);			/* Send CR-LF sequence	    */
	    	prtnmac(LF);			/*   (zeros column)	    */
	    	inscrlf();
	    	printnmbase();			/* Print line # if required */
	    	break;

	      case CTL_R:			/* Redisplay line so user   */
	        crlf();				/*   can see how it looks   */
		printnmbase();			/* Line number if needed    */
	        typelines();
	        break;

	      case CTL_U:			/* Delete line, teletype    */
	        setlimits();			/*   style.  Restore indices*/
	        setptrs();			/*   to line start, print   */
	        crlf();				/*   line number on new line*/
	        printnmbase();
	        break;

	      case CTL_X:
	        setlimits();			/* Delete line, screen syle */
	        setptrs();			/* Restore indices to line  */
	        while (column > scolumn)	/*   start, backspace to    */
	    		backspace();		/*   start column	    */
	        break;

	      case RUBOUT:			/* Character delete, tty    */
	        ins_error_chk();		/*   style.  Reasonable?    */
	        decfront();			/* Apparently.  Print out & */
	        printc(chr = base[front]);	/*   forget most recently   */
	        break;				/*   entered character	    */

	      case CTL_L:			/* Control-L in single-line */
		if (! inserting)		/*   command interpreted as */
		{				/*   as new-line sequence   */
		    inscrlf();
		    break;			/* NOTE: falls through to   */
		}				/*   default if inserting!  */

	      default:				/* End of special cases	    */

		insert();			/* Just insert character and*/
		prtnmac(chr);			/*   echo for anything else  */
						/*   (accomodates tabs, ctl */
						/*   chars, adjusts column) */
		
		tcolumn = 0;			/* Allow CTL_H, DEL 	    */
	    }					/* End of switch	    */
	}					/* End of while (scanning())*/

	if (chr != ENDFILE)			/* Not ENDFILE: must be CR  */
	{					/*   at end of single line  */
	    inscrlf();				/*   command.  Put in buffer*/
	    column = 0;
	}
	if (inserting && lineset)		/* Was unneeded # printed?  */
	    crlf();				/* Go to new line	    */
}



		/********************************/
		/*		 		*/
		/*       R E A D _ L I B        */
		/*		 		*/
		/********************************/
VOID
read_lib()					/* Read library file: R cmd */
{
	static BOOLEAN	reading	= FALSE;	/* TRUE if reading rfcb	    */

	setrdma();				/* Tell BDOS where data goes*/
	if (flag = parse_lib(&rfcb))		/* Is it R<filename> command*/
		reading = FALSE;		/* If so, must open file    */
	if (! reading)				/* Need to open file?	    */	
	{
		if (! flag)			/* Use transfer file name   */
			move(12, (char *) &xfcb, (char *) &rfcb);
		rfcb.record = 0;		/* Read from beginning	    */
		rfcb.extent = 0;
		rbp = SECTSIZE;			/* 1st readfile forces read */
		open(&rfcb);
		reading = TRUE;
	}
	while ((chr = readfile()) != ENDFILE)	/* Read and insert data	    */
		insert();
	reading = FALSE;
	_close(&rfcb);
}


		/********************************/
		/*		 		*/
		/*           J U X T            */
		/*		 		*/
		/********************************/

VOID						/* Juxtapose operation:	    */
juxt()						/*   search for first string*/
{						/*   insert second, delete  */
	UWORD	 wbj, t;			/*   until third	    */

	setfind();				/* Get the search string, & */
	collect();				/*   substitute string	    */
	wbj = wbe;				/* Note where it ends	    */
	collect();				/* Get terminating string   */
						/* Search for string at 0 to*/
						/*   (wbp - 1), insert	    */
						/*   string at wpb to	    */
						/*   (wbj - 1) and terminat-*/
						/*   ing string at wbj to   */
						/*   (wbe -1)  (Phew!)	    */
	while (distnzero())			/* While lines in buffer    */
	{
		chkfound();			/* Find match, abort if none*/
		mi = wbp - 1;			/* Got a match.  Insert	    */
		while (++mi < wbj)		/*   string here	    */
		{
			chr = scratch[mi];
			insert();
		}

		t = front;			/* Save match start position*/
		if (! find(wbj, wbe))		/* Look for terminator	    */
						/* Abort if not found	    */
			longjmp(main_env, OVERCOUNT);

		first = front - (wbe - wbj);	/* Found it.  Move it back  */
		direction = BACKWARD;		/*   so it follows last	    */
		mover();			/*   inserted character	    */

		setfront(t);			/* Position at start of	    */
	}					/*   inserted string	    */
}


		/********************************/
		/*		 		*/
		/*           N E X T            */
		/*		 		*/
		/********************************/

VOID						/* Next command: search	    */
next()						/*   whole file for a match,*/
{						/*   not just edit buffer   */
	UWORD	tdist;				/* place to save distance   */

	setfind();				/* Collect the search string*/
	while (distnzero())			/* While data in buffer	    */
	{
		while (! find(0, wbp))		/* While string not found   */
		{
			if (break_key())	/* Allow user to abort	    */
				longjmp(main_env, RESET);

			tdist = distance;	/* Save distance, then write*/
			clearmem();		/*   buffer to disk	    */
			apphalf();		/* Get half a bufferfull    */
			direction = BACKWARD;	/* Move to base of buffer   */
			first = 1;
			mover();
			distance = tdist;	/* Restore distance and	    */
			direction = FORWARD;	/*   prepare to search	    */
			if (back >= maxm)	/* If end of file, abort    */
				longjmp(main_env, OVERCOUNT);
		}
	}
}



/****************************************************************************/
/*									    */
/*	               C O M M A N D   P A R S I N G                        */
/*	               -----------------------------                        */
/*									    */
/****************************************************************************/




		/********************************/
		/*		 		*/
		/*       R E A D C T R A N      */
		/*		 		*/
		/********************************/
VOID
readctran()					/* Get a char, translate to */
{						/*   uppercase.  If char was*/
	BOOLEAN	t;				/*   uppercase anyway, or if*/
						/*   upper flag set, set    */
						/*   flag so whole line will*/
						/*   be translated	    */

	translate = FALSE;			/* Do not translate to u/c  */
	chr = readc();
	t = lowercase(chr);			/* Is character lowercase?  */
	translate = TRUE;			
	chr = utran(chr);			/* Make sure it's uppercase */
	translate = (upper || (!t));		/* Translate following	    */
}						/*   characters to u/c?	    */



		/********************************/
		/*		 		*/
		/*        S N G L C O M         */
		/*		 		*/
		/********************************/

						/* TRUE if command is only  */
						/*   char, not in macro or  */
						/*   combination on a line  */
						/* NOTE: this is a macro    */

#define snglcom(c)	((chr == (c)) && (combuf.comlen == 1) && (mp == 0))



		/********************************/
		/*		 		*/
		/*        S N G L R C O M       */
		/*		 		*/
		/********************************/
BOOLEAN 
snglrcom()					/* Return TRUE if user	    */
{						/*   responds "Y" to a Y/N  */
	int	j;				/*   request.  Aborts if    */
						/*   answer is "N"	    */
	if (! snglcom(chr))
		return (FALSE);
	FOREVER					/* Until user enters "Y"    */
	{					/*   or "N"		    */
		crlf();
		printch(chr);
		_print("-(Y/N)? $");
		printc(j = ucase((int) _conio(0xff)));
		crlf();
		switch (j)
		{
		  case 'N':
			longjmp(main_env, GET_LINE);
		  case 'Y':
			return(TRUE);
		}
	}
}



		/********************************/
		/*		 		*/
		/*         N U M B E R          */
		/*		 		*/
		/********************************/
VOID
number()					/* Set distance to the	    */
{						/*   number (if any) on cmd */
	int	i;				/*   line, else 0.  On exit */
						/*   chr holds cmd char	    */
	distance = 0;
	while (((i = ((int) chr) - '0') <= 9)	/* Uses signed arithmetic!  */
	      && (i >= 0))
	{					/* While digits read...	    */
		distance = 10 * distance + i;
		readctran();
	}
}


		/********************************/
		/*		 		*/
		/*     R E L D I S T A N C E    */
		/*		 		*/
		/********************************/
VOID
reldistance()					/* Change to distance from  */
{						/*   absolute value to value*/
	if (distance > baseline)		/*   relative to current    */
	{					/*   line		    */
		direction = FORWARD;
		distance -= baseline;
	} 
	else
	{
		direction = BACKWARD;
		distance = baseline - distance;
	}
}


/****************************************************************************/
/*									    */
/*	               C O M M A N D   A C T I O N S                        */
/*	               -----------------------------                        */
/*									    */
/****************************************************************************/



		/********************************/
		/*		 		*/
		/*         S I M P L E          */
		/*		 		*/
		/********************************/
VOID
simple()					/* Actions the "simple"	    */
{						/*   commands: those which  */
						/*   may not be preceded by */
						/*   a number.  These are:  */
						/*   E, H, I, O, Q, R	    */

	switch(chr)				/* Some commands MUST be    */
	{					/*   alone on the line for  */
	  case 'E': case 'H':			/*   safety.  If they are   */
		if(!snglcom(chr))   		/*   not, flag error.  If   */
			longjmp(main_env,BADCOM);/*  'E' or 'H', must be    */
		break;				/*   alone on line but need */
						/*   not be confirmed.  If  */
	  case 'O': case 'Q':			/*   'O' or 'Q', then user  */
		if (! snglrcom())		/*   is asked to confirm    */
			longjmp(main_env, BADCOM);/*   them.		    */
	}					/*   (get new line if not   */
						/*   confirmed in snglrcom) */

	switch(chr)				/* Now take appropriate	    */
	{					/*   action for command	    */
	  case 'E':				/* End edit normally	    */
		terminate();			/* Write & rename O/P file  */
		_exit(0);			/* Reboot CP/M		    */	


	  case 'H':				/* Go to top (head).  This  */
		terminate();			/*   is like ending edit    */
		newfile = FALSE;		/*   then starting new edit */
		if (onefile)			/*   on file just written   */
		{				/* If source & dest names   */
			chr = SFCB->drive;	/*   the same, they may be  */
			SFCB->drive = dfcb.drive;/*  on different disks:    */
			dfcb.drive = chr;	/*   swap drive names	    */
		}
		else				/* Src & dest names differ: */
		{				/*   new src name is old    */
			*SFCB = dfcb;		/*   dest file name	    */
			onefile = TRUE;		/* Src & dest now the same  */
		}
		longjmp(main_env, RESTART);	/* Start new edit	    */
	
	  case 'I':				/* Insert characters	    */
		insert_chars();
		break;
	
	  case 'O':				/* Re-edit old file: forget */
		_close(SFCB);			/*   any changes made	    */
		longjmp(main_env, RESTART);

	  case 'R':				/* Read from .LIB file      */
		read_lib();
		break;
	
	  case 'Q':				/* Quit: abort edit session */
		delete_file(&dfcb);		/* Delete .$$$ dest file    */
		if (newfile || (! onefile))	/* Is there correctly named */
		{				/*   dest file?  If so,	    */
			settype(&dfcb, dtype);	/*   delete that too	    */
			_delete(&dfcb);
		}
		_exit(0);			/* Reboot CP/M		    */
	}
}




		/********************************/
		/*		 		*/
		/*     C O N T R O L L E D      */
		/*		 		*/
		/********************************/
VOID
controlled()					/* Actions the "controlled" */
{						/*   commands: those which  */
						/*   may be preceded by a   */
						/*   direction and/or line  */
						/*   number.  These are:    */
						/*   B, C, D, K, L, P, T,   */
						/*   U, V, <CR>		    */

	switch(chr)				/* Take appropriate action  */
	{
	  case 'B':				/* Beginning/bottom of buff */
						/*   according to dir'n	    */
						/* Reverse dir'n for move   */
		direction = (direction == FORWARD) ?
			      BACKWARD : FORWARD;
		first = 1;			/* Move whole buffer	    */
		lastc = maxm;
		mover();
		break;
	
	  case 'C':				/* Move character positions */
		setclimits();			/* Map distance to buffer   */
		mover();			/*   positions, do move	    */
		break;
	
	  case 'D':				/* Delete characters	    */
		setclimits();			/* Map distance to buffer   */
		setptrs();			/*   position, adjust ptrs  */
		break;
	
	  case 'K':				/* Kill lines		    */
		setlimits();			/* Map distance to buffer   */
		setptrs();			/*   position, adjust ptrs  */
		break;
	
	  case 'L':				/* Move line position	    */
		movelines();
		break;
	
	  case 'P':				/* Print page/pages	    */
		if (distance == 0)		/* Only one?		    */
		{				/* Just print it	    */
			direction = FORWARD;
			distance = lpp;		/* Get screen height	    */
			typelines();
		}
		else				/* More than one page	    */
		{				/* Pause after each: give   */
			while (distnzero())	/*   user chance to break in*/
			{
				page();	
				wait();
			}
		}
		break;
	
	  case 'T':				/* Type lines		    */
		typelines();
		break;
	
	  case 'U':				/* Map lower to upper case  */
		upper = (direction == FORWARD);	/*   (turn off mapping if   */
		break;				/*    -U entered)	    */
	
	  case 'V':				/* Verify (print) line #'s  */
		if (distance == 0)		/* 0V is special case:	    */
		{
			printvalue(back - front);/*Print space available    */
			printc('/');		/*   in and size of memory  */
			printvalue(maxm);	/*   buffer		    */
			crlf();
		}
		else				/* Normal case: set lineset */
		{				/*   according to direction;*/
						/*   set correct startcolumn*/
			scolumn = (lineset = (direction == FORWARD)) ?
				    8 : 0;
		}
		break;
	
	  case CR:				/* CR character: Interpret  */
		if ((mi == 1) && (mp == 0))	/*   as nLT if no command   */
		{				/*   follows		    */
			movelines();
			direction = FORWARD;
			distance = 1;
			typelines();
		}
		break;
	}
}
		

		/********************************/
		/*		 		*/
		/*       R E P E A T E D        */
		/*		 		*/
		/********************************/
VOID
repeated()					/* Actions the "repeated"   */
{						/*   commands.  These may be*/
						/*   preceded by a count,   */
						/*   but not a direction.   */
						/*   They are:		    */
						/*   A, F, M, N, S, W, X, Z */

	switch (chr)				/* Take appropriate action  */
	{
	  case 'A':				/* Append lines from source */
		direction = FORWARD;		/* Move all text to base of */
		first = front;			/*   buffer		    */
		lastc = maxm;
		mover();
		if (distance == 0)		/* User wants to append as  */
		{				/*   much as management will*/
			apphalf();		/*   allow (half of buffer) */
		}
		else				/* User gave a specific	    */
		{				/*   line count		    */
			while (distnzero())
				readline();
		}
		direction = BACKWARD;		/* Split buffer as before   */
		mover();
		break;
	
	  case 'F':				/* Find a string	    */
		setfind();			/* Set up string in temp buf*/
		while (distnzero())		/* Look for it		    */
			chkfound();
		break;
	
	  case 'J':				/* Juxtapose strings: see   */
		juxt();				/*   juxt() function for    */
		break;				/*   explanation!	    */
	
	  case 'M':				/* Macro: repeat actions    */
		if (mp != 0)			/*   following "distance"   */
			longjmp(main_env, BADCOM);/* times.  Macros cannot  */
						/*   be nested		    */
						/* Copy macro to buffer     */
		for (xp = 0; (macro[xp++] = readc()) != CR;)
		{
			if (xp == sizeof macro) /* Beware overflow!	    */
				longjmp(main_env, OVERFLOW);
		}
		mp = xp - 1;			/* Point to end of macro    */
		xp = 0;				/*   and beginning	    */
						/* Set repeat count: 0M, 1M,*/
						/*   M all mean 1M	    */
		mt = (distance > 1) ? distance : 1;
		break;

	  case 'N':				/* Search whole file for    */
		next();				/*   next match of string   */
		break;
	
	  case 'S':				/* Substitute string	    */
		setfind();			/* Set up the string to find*/
		collect();			/*   and to substitute	    */
		while(distnzero())		/* Repeat the following:    */
		{
			chkfound();		/* Find a match		    */
						/* Delete matched string    */
			setfront(front - (mi = wbp));
			while (mi < wbe)	/* Insert replacement string*/
			{
				chr = scratch[mi++];
				insert();
			}
		}
		break;
	
	  case 'W':				/* Write out buffer	    */
		writeout();
		break;
	
	  case 'X':				/* Copy lines into transfer */
		flag = parse_lib(&rfcb);	/*   file (for block move)  */
		if (distance == 0)		/* 0X means delete the file */
		{
			xferon = FALSE;		/* Transfer file inactive   */
			_delete(&rfcb);		/* Delete it		    */
			if (dcnt == 0xff)	/* Grouse if it's not there */
				perror(not_found);
		}
		else				/* Not 0X: write the file   */
		{
			wrt_xfer();
		}
		close_xfer();
		break;
	
	  case 'Z':				/* Sleep.  If 0Z, wait until*/
		if (distance == 0)		/*   any key entered.  CTL_Y*/
		{				/*   junks current cmd line */
			if (break_key())
				longjmp(main_env, RESET);
		}
		else				/* Sleep "distance" quarter */
		{				/*   seconds, allowing user */
			while (distnzero())	/*   to abort 		    */
				wait();
		}
	}
}


/****************************************************************************/
/*									    */
/*			I N I T I A L I Z A T I O N			    */
/*									    */
/*	How does ED allocate memory?  Like this (not to scale):		    */
/*									    */
/*	+-------------------------------+  <--- Address zero		    */
/*	|				|				    */
/*	|	       E D		|  	Only this area is allocated */
/*	|				|	when ED is initially loaded */
/*	|  C O D E ,  D A T A ,  B S S  |	by CP/M (ED is very approx  */
/*	|				|	12k in length)		    */
/*	|				|				    */
/*	+-------------------------------+  <--- base points here	    */
/*	|				|				    */
/*	|     E D I T   B U F F E R	|	Allocated by allocte_memory.*/
/*	|				|				    */
/*	|				|  <--- base[hmax] halfway up buffer*/
/*	|    3/4 of space between base  |				    */
/*	|	 and top of stack	|  <--- base[maxm] penultimate byte */
/*	|				|  <--- base[max] is last byte	    */
/*	+-------------------------------+  <--- dbuffadr points here	    */
/*	|				|				    */
/*	|    Destination File Buffer	|	Allocated by allocate_memory*/
/*	| 1/8 space between base & tos	|				    */
/*	|				|  <--- dbuffaddr[bufflength - 1]   */
/*	+-------------------------------+  <--- sbuffadr points here	    */
/*	|				|				    */
/*	|	Source File Buffer	|	Allocated by allocate_memory*/
/*	| 1/8 space between base & tos	|				    */
/*	|				|  <--- sbuffaddr[bufflength - 1]   */
/*	+-------------------------------+				    */
/*	|XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX|	Stack growth allowance	    */
/*	+-------------------------------+				    */
/*	|				|				    */
/*	|	   User stack		|				    */
/*	|				|				    */
/*									    */
/****************************************************************************/



		/********************************/
		/*		 		*/
		/* A L L O C A T E _ M E M O R Y*/
		/*		 		*/
		/********************************/
VOID
allocate_memory()				/* Carve up free memory	    */
{
	max = (UWORD) _base->freelen - MARGIN;	/* How much space can we get*/
	if ((int) (base = sbrk(max)) == -1)	/* Grab the lot!	    */
	{
	    perror("Can't allocate memory$");	/* "This should never	    */
	    _exit(1);				/*   happen" (famous last   */
	}					/*   words!)		    */

#ifdef DEBUG
	fill(base, 0xa5, max);			/* Make buff changes visible*/
#endif
	nbuf = ((max / SECTSIZE) / 8) - 1;	/* Allocate 2 I/O buffers,  */
						/*  each of (nbuff + 1)	    */
	bufflength = (nbuf + 1) * SECTSIZE * 2;	/*  sectors, and each taking*/
	if ((bufflength + 1024) > max)		/*  1/8 of free memory	    */
	{
	    perror("Insufficient memory$");	/* No room left for edit    */
	    _exit(1);				/*   buffer		    */
	}
	max -= (bufflength + 1);		/* Adjust max to index byte */
						/*   below I/O buffer	    */
	base[max] = 0;				/* Stop match at buffer end */
	maxm = max - 1;				/* maxm is max valid index  */
	hmax = maxm / 2;			/* hmax is halfway up buffer*/

	bufflength /= 2;			/* Divide disk buff between */
	sbuffadr = &base[max + 1];		/*   source buff at sbuffadr*/
	dbuffadr = &sbuffadr[bufflength];	/*   & dest buff at dbuffadr*/
}


		/********************************/
		/*		 		*/
		/*   S E T _ U P _ F I L E S    */
		/*		 		*/
		/********************************/
VOID
set_up_files()					/* Determine source and	    */
{						/*   destination filenames  */
	if (fcb1->fname[0] == ' ')		/* Was file named on command*/
	{					/*   line?  If not, request */
	   print("Input file? $");		/*   one		    */
	   readcom();
	   printc(LF);
	   tail = FALSE;			/* Flag command line empty  */
	}
	if (! parse_fcb(SFCB))			/* Reboot if not valid name */
	    reboot();

	if (has_bdos3)				/* For version 3 BDOS,	    */
	{					/*   provisionally give dest*/
	    copydest();				/*   same name and password */
	    _get_xfcb(&dfcb);			/*   protection mode as src */
	    protection = dfcb.extent;
	    if ((ver & 0xff00) == 0)		/* For CP/M 80, try to read */
						/*   lines per page	    */
	    	if ((lpp = _gset_scb(pb)) == 0)
		    lpp = 23;			/* Failure: use default	    */
	}
	setdest();				/* Parse destination file   */
	tail = FALSE;				/* Finished with cmd line   */


	/* If source and destination disks differ, check for an existing    */
	/*   source file on the destination disk - there could be a fatal   */
	/*   error condition which could destroy a file if the user happened*/
	/*   to be addressing the wrong disk				    */

	if (((! onefile)			/* Input and output not same*/
	   || (SFCB->drive != dfcb.drive))	/* On different disks?	    */
	   && (_open(&dfcb) != 255))		/* Try to open output file  */
		abort("output file exists, erase it$");
}


/****************************************************************************/
/*									    */
/*	                M A I N   P R O G R A M				    */
/*	                -----------------------                             */
/*									    */
/* NOTE: this function is named _main, not main, so as to avoid the	    */
/*	 standard C prolog function (also called _main) being pulled in	    */
/*	 from the library at link-edit time.  The library function would    */
/*	 add a considerable amount of dead wood - notably several parts	    */
/*	 of the standard I/O package - which are not required by ED	    */
/*									    */
/****************************************************************************/

VOID
_main()
{
	int reason;				/* Internal error code	    */

	ver = _version();			/* Where are we?  What	    */
	has_bdos3 = ((ver & 0x00ff) >= CPM3);	/*   facilites are there?   */
						/* BDOS 3 has passwds, xfcbs*/

	allocate_memory();
	fcb1 = &_base->fcb1;			/* Initialize pointers to   */
	buff = _base->buff;			/*   basepage structures    */
	set_up_files();


		/********************************/
		/*		 		*/
		/*         Error recovery       */
		/*		 		*/
		/********************************/

	/* The following control structure is used in error recovery:	    */
	/*   serious errors pass control to the top of the switch by means  */
	/*   of longjmp(main_env, ERROR_CODE).  Control then passes to the  */
	/*   switch label corresponding to ERROR_CODE.			    */

	switch (reason = setjmp(main_env))
	{
	case RESTART:				/* Junk buffer contents (if */
	    setup();				/*   any) and start edit    */
	    base[0] = LF;			/*   from the top	    */
	    front = 1;
	    back = maxm;
	    column = 0;
	    break;

	case GET_LINE:				/* Just get new command	    */
	    break;
	
	case OVERCOUNT:				/* Command fell out of buff */
	    flag = POUND;
	    break;
	
	case BADCOM:				/* Invalid command	    */
	    flag = WHAT;
	    break;
	
	case OVERFLOW:				/* I, F, or S command hit   */
	    flag = '>';				/*   end of memory	    */
	    break;
	
	case DISK_ERR:				/* Disk is irrevocably full */
	    flag = 'F';
	    err_msg = diskfull;
	    break;
	
	case DIR_ERR:				/* Can't create file:	    */
	    flag = 'F';				/*   directory full	    */
	    err_msg = dirfull;
    
	case RESET:				/* Clean up after error	    */
	    break;
	}					/*   (see next comment)	    */

	if (reason > GET_LINE)			/* Did something go wrong?  */
	{
	    printsuppress = FALSE;
	    print("\tBreak \"$");		/* Say what went wrong	    */	
	    printc(flag);
	    _print("\" at $");
	    if ((chr == CR) || (chr == LF))
	    {
		_print("end of line$");
	    }
	    else
	    {
		printc(chr);			/* On which command	    */
	    }
	    if (err_msg != NULL)		/*   and why		    */
	    {
	    	perror(err_msg);
	    	err_msg = NULL;
	    }
	    crlf();
	}
	
	readbuff = TRUE;			/* Need to read new command */
	mp = 0;					/* Forget about any macro   */


		/********************************/
		/*		 		*/
		/*      Main Processing Loop    */
		/*		 		*/
		/********************************/

	FOREVER					/* Until unrecoverable error*/
	{					/*   or user wants out	    */	
	    inserting = FALSE;			/* Clear text insert mode   */
	    readctran();			/*   Get character	    */
	    flag = 'E';
	    mi = combuf.cbp;			/* Save command start addr  */

	    /****************************************************************/
	    /*								    */
	    /* SIMPLE COMMANDS (cannot be preceded by direction/distance)   */
	    /*								    */
	    /*	E	End the edit normally				    */
	    /*	H	Move to head of edited file			    */
	    /*  I	Insert characters				    */
	    /*  O	Return to the original file (junk any changes)	    */
	    /*  Q	Quit edit without change to original file	    */
	    /*	R	Read from library file				    */
	    /*								    */
	    /****************************************************************/

	    switch(chr)
	    {
	      case 'E':	case 'H': case 'I': case 'O': case 'Q': case 'R':

		simple();			/* Go honor command	    */
		continue;			/* Return to FOREVER	    */
	    }

		/********************************/
		/*		 		*/
		/*     Not a simple command     */
		/*                              */
		/*  Could be a count or number  */
		/*		 		*/
		/********************************/

	    direction = FORWARD;		/* Direction provisionally  */
	    distance = 1;			/*   forward, over 1 item   */

	    if (chr == '-')			/* Direction is backward    */
	    {
		readctran();			/* Read another character   */
		direction = BACKWARD;
	    }

	    switch(chr)				/* Direction set: how far?  */
	    {
	      case POUND:			/* "As far as you can go"   */
		distance = 0xffff;
		readctran();			/* Read another character   */
		break;

	      case '0': case '1': case '2': case '3': case '4':
	      case '5': case '6': case '7': case '8': case '9':

		number();			/* Get the count.  If colon */
		if (chr == ':')			/*   follows, it's absolute */
		{				/* Treat as "Move to Line"  */
		    chr = 'L';
		    reldistance();
		}
		break;

	      case ':':				/* Absolute (rather than    */
		readctran();			/*   relative) line number  */
		number();
		reldistance();			/* Convert to relative	    */
		if (direction == FORWARD)
		    distance++;
	    }

	    if (distance == 0)
		direction = BACKWARD;

	    switch(chr)				/* Should be at command now */
	    {
	      case 0:				/* Could be null left by    */
		break;				/*   last stop: discard it  */

	    /****************************************************************/
	    /*								    */
	    /* CONTROLLED EFFECT COMMANDS (may have direction and distance) */
	    /*								    */
	    /*	B	Move to Beginning/Bottom of buffer		    */
	    /*	C	Move Character positions			    */
	    /*	D	Delete characters				    */
	    /*	K	Kill lines					    */
	    /*	L	Move Line position				    */
	    /*	P	Page up or down lpp lines and print		    */
	    /*	T	Type lines					    */
	    /*	U	Upper case translate				    */
	    /*	V	Verify line numbers				    */
	    /*  <CR>	Move up or down lines and print			    */
	    /*								    */
	    /****************************************************************/


	      case 'B': case 'C': case 'D': case 'K': case 'L':
	      case 'P': case 'T': case 'U': case 'V': case CR:

		controlled();			/* Execute the command	    */
		break;				/* Go back to FOREVER	    */


	    /****************************************************************/
	    /*								    */
	    /* REPEATED COMMANDS (allow only a preceding number)	    */
	    /*								    */
	    /*	A	Append lines from source file to buffer		    */
	    /*	F	Find n'th occurrence of match string		    */
	    /*  J	Juxtapose (combined replace & delete)		    */
	    /*	M	Apply Macro					    */
	    /*	N	Same as F, but scan whole file, not just buffer	    */		    /*	S	Perform n Substitutions				    */
	    /*  W	Write lines to the output file			    */	
	    /*	X	Transfer (Xfer) lines to temp file		    */
	    /*	Z	Sleep (ZZZZ)					    */
	    /*								    */
	    /****************************************************************/

	      case 'A': case 'F': case 'J': case 'M': case 'N':
	      case 'S': case 'W': case 'X': case 'Z':

		if ((direction == FORWARD)	/* Not valid commands if    */
		   || (distance == 0))		/*  not just number precedes*/
		{
		    repeated();			/* OK to do command	    */
		    break;			/* Go back to FOREVER	    */
		}				/* NOTE: else case falls    */
						/*   thru to default below  */


	    /****************************************************************/
	    /*								    */
	    /* NONE OF THE ABOVE					    */
	    /*								    */
	    /*	Not a valid command letter: error			    */
	    /*								    */
	    /****************************************************************/

	      default:
		longjmp(main_env, BADCOM);	/* Jump out to error handler*/
	    }					/* End of switch (chr)	    */

	}					/* End of FOREVER	    */
}
