Files
Sepp J Morris 31738079c4 Upload
Digital Research
2020-11-06 18:50:37 +01:00

3428 lines
102 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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