From 2ee4787d1df9784d40a7eda91e61691da3d56f74 Mon Sep 17 00:00:00 2001 From: paulcantrell Date: Mon, 10 Dec 2007 21:59:20 +0000 Subject: Initial revision --- config.h | 225 +++++ poll.h | 19 + syspoll.h | 153 ++++ tecbuf.c | 2710 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ teccmd.c | 2668 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tecdebug.c | 401 +++++++++ tecdisp.c | 2867 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tecexec.c | 2801 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tecmem.c | 456 ++++++++++ teco.c | 1888 +++++++++++++++++++++++++++++++++++++++ teco.h | 657 ++++++++++++++ tecparse.c | 2178 +++++++++++++++++++++++++++++++++++++++++++++ tecparse.h | 327 +++++++ tecstate.c | 2374 +++++++++++++++++++++++++++++++++++++++++++++++++ tecterm.c | 1579 +++++++++++++++++++++++++++++++++ tecundo.c | 504 +++++++++++ tecvms.h | 51 ++ 17 files changed, 21858 insertions(+) create mode 100644 config.h create mode 100644 poll.h create mode 100644 syspoll.h create mode 100644 tecbuf.c create mode 100644 teccmd.c create mode 100644 tecdebug.c create mode 100644 tecdisp.c create mode 100644 tecexec.c create mode 100644 tecmem.c create mode 100644 teco.c create mode 100644 teco.h create mode 100644 tecparse.c create mode 100644 tecparse.h create mode 100644 tecstate.c create mode 100644 tecterm.c create mode 100644 tecundo.c create mode 100644 tecvms.h diff --git a/config.h b/config.h new file mode 100644 index 0000000..708cd2d --- /dev/null +++ b/config.h @@ -0,0 +1,225 @@ +/* config.h. Generated by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Define to 1 if the `closedir' function returns void instead of `int'. */ +/* #undef CLOSEDIR_VOID */ + +/* Define to 1 if `TIOCGWINSZ' requires . */ +/* #undef GWINSZ_IN_SYS_IOCTL */ + +/* Define to 1 if you have the `alarm' function. */ +#define HAVE_ALARM 1 + +/* Define to 1 if you have the `bzero' function. */ +#define HAVE_BZERO 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_CTYPE_H 1 + +/* Define to 1 if you have the declaration of `cfgetospeed', and to 0 if you + don't. */ +#define HAVE_DECL_CFGETOSPEED 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +#define HAVE_DIRENT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ERRNO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_FCNTL_H 1 + +/* Define to 1 if you have the `fork' function. */ +#define HAVE_FORK 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the `ncurses' library (-lncurses). */ +#define HAVE_LIBNCURSES 1 + +/* Define to 1 if you have the `termcap' library (-ltermcap). */ +#define HAVE_LIBTERMCAP 1 + +/* Define to 1 if you have the `terminfo' library (-lterminfo). */ +/* #undef HAVE_LIBTERMINFO */ + +/* Define to 1 if you support file names longer than 14 characters. */ +#define HAVE_LONG_FILE_NAMES 1 + +/* Define to 1 if your system has a GNU libc compatible `malloc' function, and + to 0 otherwise. */ +#define HAVE_MALLOC 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_MEMORY_H 1 + +/* Define to 1 if you have the `mkdir' function. */ +#define HAVE_MKDIR 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. */ +/* #undef HAVE_NDIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_POLL_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_PWD_H 1 + +/* Define to 1 if you have the `sbrk' function. */ +#define HAVE_SBRK 1 + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SGTTY_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SIGNAL_H 1 + +/* Define to 1 if `stat' has the bug that it succeeds when given the + zero-length file name argument. */ +/* #undef HAVE_STAT_EMPTY_STRING_BUG */ + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if `c_cflag' is member of `struct termios'. */ +#define HAVE_STRUCT_TERMIOS_C_CFLAG 1 + +/* Define to 1 if `c_ospeed' is member of `struct termios'. */ +#define HAVE_STRUCT_TERMIOS_C_OSPEED 1 + +/* Define to 1 if `sg_ospeed' is member of `struct termios'. */ +/* #undef HAVE_STRUCT_TERMIOS_SG_OSPEED */ + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_DIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_FILE_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_FILIO_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_IOCTL_H 1 + +/* Define to 1 if you have the header file, and it defines `DIR'. + */ +/* #undef HAVE_SYS_NDIR_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_PARAM_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SELECT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have that is POSIX.1 compatible. */ +#define HAVE_SYS_WAIT_H 1 + +/* Define to 1 if you have the `tcgetattr' function. */ +#define HAVE_TCGETATTR 1 + +/* Define to 1 if you have the `tcsetattr' function. */ +#define HAVE_TCSETATTR 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TERMCAP_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_TERMIO_H */ + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you have the `vfork' function. */ +#define HAVE_VFORK 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_VFORK_H */ + +/* Define to 1 if `fork' works. */ +/* #undef HAVE_WORKING_FORK */ + +/* Define to 1 if `vfork' works. */ +#define HAVE_WORKING_VFORK 1 + +/* Define to 1 if `lstat' dereferences a symlink specified with a trailing + slash. */ +/* #undef LSTAT_FOLLOWS_SLASHED_SYMLINK */ + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "paul@copters.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "VTECO" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "VTECO 6.4" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "vteco" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "6.4" + +/* Define as the return type of signal handlers (`int' or `void'). */ +#define RETSIGTYPE void + +/* Define to the type of arg 1 for `select'. */ +#define SELECT_TYPE_ARG1 int + +/* Define to the type of args 2, 3 and 4 for `select'. */ +#define SELECT_TYPE_ARG234 (fd_set *) + +/* Define to the type of arg 5 for `select'. */ +#define SELECT_TYPE_ARG5 (struct timeval *) + +/* Define to 1 if the `S_IS*' macros in do not work properly. */ +/* #undef STAT_MACROS_BROKEN */ + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Define to 1 if you can safely include both and . */ +#define TIME_WITH_SYS_TIME 1 + +/* Define to empty if `const' does not conform to ANSI C. */ +/* #undef const */ + +/* Define to rpl_malloc if the replacement function should be used. */ +/* #undef malloc */ + +/* Define to `int' if does not define. */ +/* #undef pid_t */ + +/* Define as `fork' if `vfork' does not work. */ +/* #undef vfork */ diff --git a/poll.h b/poll.h new file mode 100644 index 0000000..ade728d --- /dev/null +++ b/poll.h @@ -0,0 +1,19 @@ +/* Copyright (c) 1988 AT&T */ +/* All Rights Reserved */ + +/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ +/* The copyright notice above does not evidence any */ +/* actual or intended publication of such source code. */ + +#ifndef _POLL_H +#define _POLL_H + +#pragma ident "@(#)poll.h 1.7 92/07/14 SMI" /* SVr4.0 1.2 */ + +/* + * Poll system call interface definitions. + */ + +#include "syspoll.h" + +#endif /* _POLL_H */ diff --git a/syspoll.h b/syspoll.h new file mode 100644 index 0000000..68e4884 --- /dev/null +++ b/syspoll.h @@ -0,0 +1,153 @@ +/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ +/* All Rights Reserved */ + +/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */ +/* The copyright notice above does not evidence any */ +/* actual or intended publication of such source code. */ + +/* + * Copyright (c) 1995 by Sun Microsystems, Inc. + * All rights reserved. + */ + +#ifndef _SYS_POLL_H +#define _SYS_POLL_H + +#pragma ident "@(#)poll.h 1.24 97/04/18 SMI" /* SVr4.0 11.9 */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Structure of file descriptor/event pairs supplied in + * the poll arrays. + */ +typedef struct pollfd { + int fd; /* file desc to poll */ + short events; /* events of interest on fd */ + short revents; /* events that occurred on fd */ +} pollfd_t; + +typedef unsigned long nfds_t; + +/* + * Testable select events + */ +#define POLLIN 0x0001 /* fd is readable */ +#define POLLPRI 0x0002 /* high priority info at fd */ +#define POLLOUT 0x0004 /* fd is writeable (won't block) */ +#define POLLRDNORM 0x0040 /* normal data is readable */ +#define POLLWRNORM POLLOUT +#define POLLRDBAND 0x0080 /* out-of-band data is readable */ +#define POLLWRBAND 0x0100 /* out-of-band data is writeable */ + +#define POLLNORM POLLRDNORM + +/* + * Non-testable poll events (may not be specified in events field, + * but may be returned in revents field). + */ +#define POLLERR 0x0008 /* fd has error condition */ +#define POLLHUP 0x0010 /* fd has been hung up on */ +#define POLLNVAL 0x0020 /* invalid pollfd entry */ + +#ifdef _KERNEL + +/* + * Additional private poll flags supported only by strpoll(). + * Must be bit-wise distinct from the above POLL flags. + */ +#define POLLRDDATA 0x200 /* Wait for M_DATA; ignore M_PROTO only msgs */ +#define POLLNOERR 0x400 /* Ignore POLLERR conditions */ + +#endif /* _KERNEL */ + +#if defined(_KERNEL) || defined(_KMEMUSER) + +#include +#include + +/* + * Poll list head structure. A pointer to this is passed to + * pollwakeup() from the caller indicating an event has occurred. + * Only the ph_list field is used, but for DDI compliance, we can't + * change the size of the structure. + */ +typedef struct pollhead { + struct polldat *ph_list; /* list of pollers */ + struct polldat *ph_dummy; /* unused -- see above */ + short ph_events; /* unused -- see above */ +} pollhead_t; + +/* + * Data necessary to notify process sleeping in poll(2) + * when an event has occurred. + */ +typedef struct polldat { + kthread_t *pd_thread; /* thread doing poll */ + int pd_events; /* events being polled */ + struct polldat *pd_next; /* next in poll list */ + struct polldat *pd_prev; /* previous in poll list */ + struct pollhead *pd_headp; /* backpointer to head of list */ + struct pollhead *pd_sphp; /* stored pollhead struct pointer */ +} polldat_t; + +/* + * State information kept by each polling thread + */ +typedef struct pollstate { + int ps_nfds; + int ps_flag; + pollfd_t *ps_pollfd; + polldat_t *ps_polldat; + kmutex_t ps_lock; /* mutex for the polling thread */ + kmutex_t ps_no_exit; /* protects ps_busy*, can't be nested */ + int ps_busy; /* can only exit when its 0 */ + kcondvar_t ps_busy_cv; /* cv to wait on if ps_busy != 0 */ + kcondvar_t ps_cv; /* cv to wait on if needed */ +} pollstate_t; + +/* ps_flag */ +#define T_POLLTIME 0x01 /* poll timeout pending */ +#define T_POLLWAKE 0x02 /* pollwakeup() occurred */ + +#if defined(_KERNEL) + +/* + * Routine called to notify a process of the occurrence + * of an event. + */ +extern void pollwakeup(pollhead_t *, short); +/* + * pollwakeup_safe will replace pollwakeup when the support + * for unsafe drivers is removed. + */ +extern void pollwakeup_safe(pollhead_t *, short); + +/* + * Internal routines. + */ +extern void polltime(kthread_t *); +extern void pollrun(kthread_t *); +extern void polldel(pollstate_t *); +extern void polllock(pollhead_t *, kmutex_t *); +extern int pollunlock(pollhead_t *); +extern void pollrelock(pollhead_t *, int); +extern void pollcleanup(kthread_t *); + +extern void ppwaitemptylist(pollhead_t *); + +#endif /* defined(_KERNEL) */ + +#endif /* defined(_KERNEL) || defined(_KMEMUSER) */ + +#if defined(__STDC__) && !defined(_KERNEL) +int poll(struct pollfd *, nfds_t, int); +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_POLL_H */ diff --git a/tecbuf.c b/tecbuf.c new file mode 100644 index 0000000..1dd93e9 --- /dev/null +++ b/tecbuf.c @@ -0,0 +1,2710 @@ +char *tecbuf_c_version = "tecbuf.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecbuf.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecbuf.c + * Subroutines to handle the edit buffers + * + * COPYRIGHT (c) 1985-2004 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +char rcs_date[] = AUTO_DATE; + +/* + * Global Storage is defined here for lack of a better place. + * First off are all the flags. + */ + +/* + * Global Variables + */ + struct buff_header *buffer_headers; + struct buff_header *curbuf; + struct buff_header *qregister_push_down_list; + +/* + * Global structures + */ + struct buff_line *line_buffer_lookaside_list = NULL; +/* + * Forward References + */ + struct buff_line *allocate_line_buffer(int); + void movc3(char *,char *,int); + void buff_buffer_map( void ); + unsigned int stringHash( char *str ); + + extern FILE *teco_debug_log; + extern int term_columns; + extern struct window *curwin; + extern char checkpoint_modified; + +/* BUFF_FIND - Find the named buffer + * + * Function: + * + * This routine is called with the name of the buffer that we want to + * find. It searches all the buffer headers until it finds the name. + * It will either return the address of the buffer header, or null if + * it could not find one with the proper name. + */ +struct buff_header * +buff_find(name) +char *name; +{ +register struct buff_header *bp; +register char *cp1,*cp2; +unsigned int hash = stringHash( name ); + + PREAMBLE(); + for(bp = buffer_headers; bp != NULL; bp = bp->next_header){ + if( hash != bp->buf_hash ) + { +// printf("skipping because hash 0x%08x != 0x%08x %s %s\n", +// hash, bp->buf_hash, name, bp->name); + continue; + } + + printf("hash match! %s %s\n",name,bp->name); +/* + * This loop implements the equivalent of strcmp, except that in the + * case of VMS, it is case insensitive. + */ + for(cp1 = name, cp2 = bp->name;;cp1++,cp2++){ +#ifdef VMS + if(UPCASE(*cp1) != UPCASE(*cp2)) break; +#else + if(*cp1 != *cp2) break; +#endif + if(*cp1 == '\0') return(bp); + }/* End FOR */ + + }/* End FOR */ + + return(NULL); + +}/* End Routine */ + + + +/* BUFF_QFIND - Find the named Q register + * + * Function: + * + * This routine is called with the name of the Q register that we + * want to find. It constructs the internal name of the Q register + * and then calls buff_find to find the buffer. If the buffer is not + * found and the create flag is set, we call buff_create to cause + * the Q register to be created. + */ +struct buff_header * +buff_qfind(name,create_flag) +char name; +char create_flag; +{ +register struct buff_header *hbp; +register int i; +char tmp_buffer[LINE_BUFFER_SIZE],tmp_message[LINE_BUFFER_SIZE]; +struct buff_header *buff_create(); + + PREAMBLE(); + (void) strcpy(tmp_buffer,TECO_INTERNAL_BUFFER_NAME); + i = strlen(tmp_buffer); + name = UPCASE((int)name); + tmp_buffer[i] = name; tmp_buffer[i+1] = '\0'; + hbp = buff_find(tmp_buffer); + if(hbp == NULL){ + if(create_flag){ + hbp = buff_create(tmp_buffer,1); + if(hbp == (struct buff_header *)NULL){ + sprintf(tmp_message,"?Cannot create Q-register %c",name); + error_message(tmp_message); + return((struct buff_header *)NULL); + }/* End IF */ + }/* End IF */ + }/* End IF */ + + return(hbp); + +}/* End Routine */ + + + +/* BUFF_CREATE - Create a new buffer + * + * Function: + * + * This routine is called when we need to create a new buffer. The caller + * should have already verified that this doesn't already exist. The + * routine returns the address of the new buffer, or NULL if there is a + * problem of some sort. + */ +struct buff_header * +buff_create(name,internal_flag) +char *name; +char internal_flag; +{ +register struct buff_header *bp; +register struct buff_header *obp; +register struct buff_line *lbp; +register int i = 0; + + PREAMBLE(); + + if(buff_find(name)) return(NULL); + +/* + * Create the buffer header itself. + */ + bp = (struct buff_header *)tec_alloc( + TYPE_C_BHDR, + sizeof(struct buff_header) + ); + + if(bp == NULL) return(NULL); + bp->buf_magic = MAGIC_BUFFER; + bp->buf_hash = stringHash( name ); + + bp->name = tec_alloc(TYPE_C_CBUFF,strlen(name)+1); + if(bp->name == NULL){ + bp->buf_magic = 0; + tec_release(TYPE_C_BHDR,(char *)bp); + return(NULL); + }/* End IF */ + (void) strcpy(bp->name,name); + + obp = buffer_headers; + if(obp) i = obp->buffer_number; + while(obp){ + if(internal_flag){ + if(obp->buffer_number < i) i = obp->buffer_number; + } + else { + if(obp->buffer_number > i) i = obp->buffer_number; + } + obp = obp->next_header; + }/* End While */ + + if(internal_flag) bp->buffer_number = i - 1; + else bp->buffer_number = i + 1; + + bp->ismodified = NO; + bp->isreadonly = NO; + bp->isbackedup = NO; + bp->dot = 0; + bp->zee = 0; + +/* + * Create the first line buffer structure + */ + lbp = allocate_line_buffer(1); + if(lbp == NULL){ + bp->buf_magic = 0; + tec_release(TYPE_C_CBUFF,bp->name); + tec_release(TYPE_C_BHDR,(char *)bp); + return((struct buff_header *)NULL); + }/* End IF */ + bp->first_line = lbp; +/* + * Initialize the rest of the special locations + */ + bp->pos_cache.lbp = NULL; + bp->pos_cache.base = 0; + +/* + * Link it into the list of buffer headers, in order by buffer number + */ + obp = buffer_headers; + while(obp){ + if(obp->next_header == NULL) break; + if(obp->next_header->buffer_number > bp->buffer_number) break; + obp = obp->next_header; + }/* End While */ + + if(obp == NULL || buffer_headers->buffer_number > bp->buffer_number){ + bp->next_header = buffer_headers; + buffer_headers = bp; + }/* End IF */ + + else { + bp->next_header = obp->next_header; + obp->next_header = bp; + }/* End Else */ + + return(bp); + +}/* End Routine */ + + + +/* BUFF_DUPLICATE - Make a duplicate of a buffer + * + * Function: + * + * This routine is called to duplicate the specified buffer and return + * a pointer to the duplicate. The current requirement for this is for + * the push Q register command ('['). + */ +struct buff_header * +buff_duplicate(sbp) +register struct buff_header *sbp; +{ +register struct buff_header *dbp; +register struct buff_line *slp; +register struct buff_line *dlp; + + PREAMBLE(); +/* + * Create the buffer header itself. + */ + dbp = (struct buff_header *) + tec_alloc(TYPE_C_BHDR,sizeof(struct buff_header)); + if(dbp == NULL){ + return((struct buff_header *)NULL); + }/* End IF */ + + dbp->name = tec_alloc(TYPE_C_CBUFF,strlen(sbp->name)+1); + if(dbp->name == NULL){ + tec_release(TYPE_C_BHDR,(char *)dbp); + return((struct buff_header *)NULL); + }/* End IF */ + + (void) strcpy(dbp->name,sbp->name); + + dbp->buf_magic = MAGIC_BUFFER; + dbp->ismodified = sbp->ismodified; + dbp->isreadonly = sbp->isreadonly; + dbp->isbackedup = sbp->isbackedup; + dbp->dot = sbp->dot; + dbp->zee = sbp->zee; + dbp->ivalue = sbp->ivalue; + dbp->pos_cache.lbp = NULL; + dbp->pos_cache.base = 0; + dbp->first_line = NULL; + +/* + * Copy the line buffer structures. If an allocation error occurs, we + * have to return all the allocated memory, and return an error. + */ + slp = sbp->first_line; + + if(slp){ + dbp->first_line = dlp = allocate_line_buffer(slp->byte_count); + if(dlp == NULL){ + buff_destroy(dbp); + return((struct buff_header *)NULL); + }/* End IF */ + movc3(slp->buffer,dlp->buffer,slp->byte_count); + dlp->byte_count = slp->byte_count; + + while((slp = slp->next_line)){ + dlp->next_line = allocate_line_buffer(slp->byte_count); + if(dlp->next_line == NULL){ + buff_destroy(dbp); + return((struct buff_header *)NULL); + }/* End IF */ + dlp->next_line->prev_line = dlp; + dlp = dlp->next_line; + movc3(slp->buffer,dlp->buffer,slp->byte_count); + dlp->byte_count = slp->byte_count; + }/* End While */ + + }/* End IF */ + + return(dbp); + +}/* End Routine */ + + + +/* MOVC3 - Copy the specified number of bytes + * + * Function: + * + * This routine copies 'n' bytes from the source to the + * destination. + */ +void +movc3(source,dest,count) +register char *source; +register char *dest; +register int count; +{ + + PREAMBLE(); + while(count-- > 0) *dest++ = *source++; + +}/* End Routine */ + + + +/* BUFF_DESTROY - Delete an existing buffer + * + * Function: + * + * This routine is called to delete an existing buffer. The routine is + * supplied the address of the buffer structure. We have to clean up + * all the lines in the buffer, all the display lines, etc. + */ +void +buff_destroy(hbp) +register struct buff_header *hbp; +{ +register struct buff_header *bp; +register struct buff_line *lbp; + + PREAMBLE(); +/* + * There are certain buffers we don't allow to be destroyed + */ + if(strcmp(hbp->name,"TECO_INTERNAL-Main") == 0){ + return; + }/* End IF */ +/* + * Clean up all the data in the buffer first + */ + lbp = hbp->first_line; + hbp->pos_cache.lbp = NULL; + hbp->first_line = NULL; + buff_free_line_buffer_list(lbp); + +/* + * Now unlink this buffer header from the header list. + * Check to see if it is at the head of the list. + */ + bp = buffer_headers; + if(bp == hbp){ + buffer_headers = bp->next_header; + }/* End IF */ + +/* + * If not at the head of the list, we have to search the list till + * we find it's father. Then we make it's father's child be it's + * child. + */ + else { + while(bp){ + if(bp->next_header == hbp){ + bp->next_header = hbp->next_header; + break; + }/* End IF */ + bp = bp->next_header; + }/* End While */ + + }/* End Else */ + +/* + * Now give back the storage for the name, and for the structure itself. + */ + if(hbp->name) tec_release(TYPE_C_CBUFF,(char *)hbp->name); + tec_release(TYPE_C_BHDR,(char *)hbp); + + return; + +}/* End Routine */ + + + +/* BUFF_FIND_LINE - Find the line structure that is on + * + * Function: + * + * This routine is called to find the line_buffer structure that + * the buffer position resides on. + */ +struct buff_line * +buff_find_line(hbp,position) +register struct buff_header *hbp; +int position; +{ +register struct buff_line *lbp; +register int i; + + PREAMBLE(); + + if(hbp == NULL) return(NULL); + +/* + * First step is to see if the current position cache in the buffer header will + * help us out. Our position must be on this line if it is to be of help to us. + */ + lbp = hbp->pos_cache.lbp; + if(lbp){ + + i = hbp->pos_cache.base + lbp->byte_count; +/* + * Test to see if it is a currently existing character position that is mapped + * by our position cache. + */ + if(position >= hbp->pos_cache.base && position < i){ + return(lbp); + }/* End IF */ +/* + * If we are inserting at the end of the buffer, the position doesn't really + * exist, but we can still resolve it if we happen to have the final line + * cached. The only case where we don't want to do this is when the line + * already has a trailing newline. Then we want to fall down into the code + * below which will recognize this and construct a new line buffer. + */ + if(position == hbp->zee && position == i){ + if(lbp->byte_count == 0) return(lbp); + if(lbp->buffer[lbp->byte_count - 1] != '\n') return(lbp); + }/* End IF */ + + }/* End IF */ + +/* + * We get here if the current cached location does not help us out. In this + * case we need to hunt till we find the correct place in the buffer. We also + * update our cache to remember this. + */ + i = position; + lbp = hbp->first_line; + + if(lbp == NULL){ + return(NULL); + }/* End IF */ + +/* + * Now, even though the cache did not give us the correct line, it can still + * help us out in the search if it maps a position before the one we are + * looking for, since we can begin our search at it's base. + */ + if(hbp->pos_cache.lbp && hbp->pos_cache.base < position){ + i -= hbp->pos_cache.base; + lbp = hbp->pos_cache.lbp; + }/* End IF */ + + while(1){ + if(lbp->byte_count >= i) break; + i -= lbp->byte_count; + if(lbp->next_line == NULL){ + char panic_string[LINE_BUFFER_SIZE]; + sprintf(panic_string, + "buff_find_line: position %d specified in buffer %s, z=%d", + position,hbp->name,hbp->zee); + tec_panic(panic_string); + printf("NULL ptr %d bytes still to go\n",i); + }/* End IF */ + lbp = lbp->next_line; + }/* End While */ + + if(i == lbp->byte_count && i && lbp->buffer[i-1] == '\n'){ + i = 0; + if(lbp->next_line == NULL){ + lbp->next_line = allocate_line_buffer(1); + if(lbp->next_line == NULL) return((struct buff_line *)NULL); + lbp->next_line->prev_line = lbp; + }/* End IF */ + lbp = lbp->next_line; + }/* End IF */ + + hbp->pos_cache.lbp = lbp; + hbp->pos_cache.base = position - i; + + return(lbp); + +}/* End Routine */ + + + +/* BUFF_FIND_OFFSET - Find offset onto line structure that is on + * + * Function: + * + * This routine is called to find the offset into the line_buffer of + * the specified position. + */ +int +buff_find_offset(hbp,lbp,position) +register struct buff_header *hbp; +register struct buff_line *lbp; +int position; +{ + + PREAMBLE(); + if(hbp == NULL) return(-1); +/* + * First step is to see if the current position cache in the buffer header will + * help us out. Our position must be on this line if it is to be of help to us. + */ + if( lbp == NULL || hbp->pos_cache.lbp != lbp){ + lbp = buff_find_line(hbp,position); + if(lbp == NULL) return(-1); + }/* End IF */ + +/* + * Now we simply calculate the position's offset from the line structure base. + */ + return( position - hbp->pos_cache.base ); + +}/* End Routine */ + + + +/* BUFF_CONTENTS - Return the character at the specified position + * + * Function: + * + * This routine is called to fetch the single character which is at + * the specified buffer position. This routine is generally called + * by routines which are low frequence or only handle a small number + * of characters at any one time. + */ +int +buff_contents(hbp,position) +register struct buff_header *hbp; +register int position; +{ +register struct buff_line *lbp; +register int i; + + PREAMBLE(); +/* + * Insure that the specified position is legal + */ + if(position > hbp->zee || position < 0){ + char panic_string[LINE_BUFFER_SIZE]; + sprintf(panic_string, + "buff_contents: illegal position %d specified in buffer %s", + position,hbp->name); + tec_panic(panic_string); + }/* End IF */ + +/* + * Call buff_find_line to find the line structure that the specified + * position resides on. + */ + lbp = buff_find_line(hbp,position); + i = buff_find_offset(hbp,lbp,position); + if(i == -1) return(-1); + + return(lbp->buffer[i] & 0xFF); + +}/* End Routine */ + +/* BUFF_CACHED_CONTENTS - Return the character and positional information + * + * Function: + * + * This routine is called to fetch the single character which is at + * the specified buffer position. This routine is generally called + * by routines which are low frequence or only handle a small number + * of characters at any one time. + */ +int +buff_cached_contents(hbp,position,cache) +register struct buff_header *hbp; +int position; +struct position_cache *cache; +{ +register struct buff_line *lbp; +register int i; + + PREAMBLE(); +/* + * Insure that the specified position is legal + */ + if(position > hbp->zee || position < 0){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"buff_contents: illegal position %d %s Z = %d\n", + position,hbp->name,hbp->zee); + tec_panic(tmp_message); + }/* End IF */ + +/* + * Call buff_find_line to find the line structure that the specified + * position resides on. + */ + lbp = buff_find_line(hbp,position); + if(lbp == NULL) return(-1); + i = buff_find_offset(hbp,lbp,position); + if(i == -1) return(-1); +/* + * If he has specified a local position cache, copy that information back to + * his local cache. It's up to him to invalidate it if the buffer gets changed. + */ + if(cache){ + cache->lbp = lbp; + cache->base = i; + }/* End IF */ + + return(lbp->buffer[i] & 0xFF); + +}/* End Routine */ + + + +/* BUFF_INIT - Routine to initialize the buffer routines + * + * Function: + * + * This routine is called before the first buffer operations to + * initialize the buffer routines. + */ +int +buff_init() +{ + register struct buff_header *hbp; + char tmp_buffer[LINE_BUFFER_SIZE]; + + PREAMBLE(); +/* + * Create the buffer map buffer + */ + (void) strcpy(tmp_buffer,TECO_INTERNAL_BUFFER_NAME); + (void) strcat(tmp_buffer,"BUFFER-MAP"); + hbp = buff_create(tmp_buffer,1); + if(hbp == NULL) return(FAIL); + hbp->buffer_number = 0; + +/* + * Create a default edit buffer + */ + (void) strcpy(tmp_buffer,TECO_INTERNAL_BUFFER_NAME); + (void) strcat(tmp_buffer,"Main"); + curbuf = hbp = buff_create(tmp_buffer,1); + if(hbp == NULL) return(FAIL); + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_OPENBUFFER - Routine to read a file into a TECO edit buffer + * + * Function: + * + * This routine is called to load a file into the named buffer. If the + * buffer already exists, we simply switch to it and don't modify it. + */ +int +buff_openbuffer(name,buffer_number,readonly_flag) +char *name; +int buffer_number; +int readonly_flag; +{ + register struct buff_header *hbp = NULL; + + PREAMBLE(); +/* + * If no name is supplied then the buffer_number argument is significant, + * otherwise we ignore it. + */ + if(name == NULL){ + hbp = buffer_headers; + while(hbp){ + if(hbp->buffer_number == buffer_number){ + name = hbp->name; + break; + }/* End IF */ + hbp = hbp->next_header; + }/* End While */ + }/* End IF */ +/* + * If no name is supplied, the buffer_number argument must already exist + * or an error is declared. + */ + if(name == NULL){ + error_message("?No such buffer number exists"); + return(FAIL); + }/* End IF */ +/* + * First determine if a buffer of that name already exists + */ + if(hbp == NULL){ + hbp = buff_find(name); + }/* End IF */ + + if(hbp){ + buff_switch(hbp,1); + return(SUCCESS); + }/* End IF */ + +/* + * If there is not such buffer in existence, we need to create one + */ + hbp = buff_create(name,0); + if(hbp == NULL) return(FAIL); + if(readonly_flag == YES) hbp->isreadonly = YES; + +/* + * Ignore possible open error, since he may be creating a new file + */ + buff_read(hbp,name); + + hbp->dot = 0; + hbp->ismodified = NO; + + buff_switch(hbp,1); + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_OPENBUFFNUM - Routine to open buffer number 'n' + * + * Function: + * + * This routine is called with the number of a buffer which is to be + * made the 'current' edit buffer. + */ +int +buff_openbuffnum(buffer_number,map_flag) +int buffer_number; +int map_flag; +{ + register struct buff_header *hbp; + + PREAMBLE(); +/* + * Search the list of buffers for the specified buffer number + */ + hbp = buffer_headers; + while(hbp){ + if(hbp->buffer_number == buffer_number){ + buff_switch(hbp,map_flag); + return(SUCCESS); + }/* End IF */ + hbp = hbp->next_header; + }/* End While */ + + return(FAIL); + +}/* End Routine */ + + + +/* BUFF_REOPENBUFF - Called by undo to re-create an edit buffer + * + * Function: + * + * This routine is called to place a buffer back onto the buffer + * list. + */ +void +buff_reopenbuff(bp) +register struct buff_header *bp; +{ +register struct buff_header *obp; + + PREAMBLE(); +/* + * Make sure there isn't another buffer of this name already here. The + * way this can happen is automatically created Q-registers like the + * search string and rubout string Q-registers might be created. Since + * they don't get undone, they would stick around and then we might + * try to undo the removal of an earlier copy of one of them. This way, + * we make sure that any earlier copy replaces any more recent copy. + */ + obp = buffer_headers; + while(obp){ + if(strcmp(obp->name,bp->name) == 0){ + buff_destroy(obp); + break; + }/* End IF */ + obp = obp->next_header; + }/* End While */ +/* + * Insert this buffer in the proper place on the buffer list. + */ + obp = buffer_headers; + while(obp){ + if(obp->next_header == NULL) break; + if(obp->next_header->buffer_number > bp->buffer_number) break; + obp = obp->next_header; + }/* End While */ + + if(obp == NULL || buffer_headers->buffer_number > bp->buffer_number){ + bp->next_header = buffer_headers; + buffer_headers = bp; + }/* End IF */ + + else { + bp->next_header = obp->next_header; + obp->next_header = bp; + }/* End Else */ + +}/* End Routine */ + + + +/* BUFF_READ - Routine to read a file into an existing TECO edit buffer + * + * Function: + * + * This routine is called to read a file into the specified edit buffer. + * The edit buffer must already exist. + */ +int +buff_read(hbp,name) +struct buff_header *hbp; +char *name; +{ + int iochan; + char tmp_message[LINE_BUFFER_SIZE]; + int status; + + PREAMBLE(); +/* + * Attempt to open the file + */ + iochan = open(name,O_RDONLY); + if(iochan < 0){ + sprintf(tmp_message,"?Cannot open <%s>: %s",name,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + status = buff_readfd(hbp,name,iochan); + close(iochan); + return(status); + +}/* End Routine */ + + + +/* BUFF_READFD - Reads the file descriptor into the specified buffer + * + * Function: + * + * This routine is called with a buffer and a file descriptor. The + * entire contents of the file descriptor are read into the specified + * buffer. + */ +int +buff_readfd(hbp,name,iochan) +struct buff_header *hbp; +char *name; +int iochan; +{ + char iobuf[IO_BUFFER_SIZE]; + char linebuf[IO_BUFFER_SIZE]; + int linebuf_cnt; + char tmp_message[LINE_BUFFER_SIZE]; + register int bcount; + register char *cp,*iop = 0; + register int i; + register struct buff_line *lbp; + struct buff_line *olbp; + int original_zee = hbp->zee; + int error_status = SUCCESS; + + PREAMBLE(); +/* + * Read the file and insert the characters into the buffer + */ + cp = linebuf; + linebuf_cnt = bcount = 0; + + while(1){ + if(bcount <= 0){ + bcount = read(iochan,iobuf,sizeof(iobuf)); + iop = iobuf; + if(bcount == 0) break; + if(bcount < 0){ + sprintf(tmp_message,"?Error reading <%s>: %s", + name,error_text(errno)); + error_message(tmp_message); + error_status = FAIL; + break; + }/* End IF */ + }/* End IF */ + + linebuf_cnt += 1; + bcount -= 1; + if((*cp++ = *iop++) == '\n' || linebuf_cnt >= sizeof(linebuf)){ + lbp = buff_find_line(hbp,hbp->dot); + if(lbp == NULL){ + error_status = FAIL; + break; + }/* End IF */ + i = buff_find_offset(hbp,lbp,hbp->dot); + if(i == -1){ + error_status = FAIL; + break; + }/* End IF */ + if(i == 0){ + olbp = lbp; + lbp = allocate_line_buffer(linebuf_cnt); + if(lbp == NULL){ + error_status = FAIL; + break; + }/* End IF */ + + lbp->next_line = olbp; + lbp->prev_line = olbp->prev_line; + olbp->prev_line = lbp; + if(lbp->prev_line) lbp->prev_line->next_line = lbp; + if(hbp->first_line == olbp) hbp->first_line = lbp; + movc3(linebuf,lbp->buffer,linebuf_cnt); + lbp->byte_count = linebuf_cnt; + hbp->pos_cache.lbp = lbp; + hbp->pos_cache.base = hbp->dot; + hbp->dot += linebuf_cnt; + hbp->zee += linebuf_cnt; + }/* End IF */ + + else { + if(buff_insert(hbp,hbp->dot,linebuf,linebuf_cnt) == FAIL){ + error_status = FAIL; + break; + }/* End IF */ + }/* End Else */ + + cp = linebuf; + linebuf_cnt = 0; + continue; + }/* End IF */ + + }/* End While */ + + if(error_status == SUCCESS && linebuf_cnt != 0){ + if(buff_insert(hbp,hbp->dot,linebuf,linebuf_cnt) == FAIL){ + error_status = FAIL; + }/* End IF */ + }/* End IF */ + +/* + * If the buffer just became modified for the first time, we need to change the + * label line to reflect this. + */ + if(hbp->ismodified == NO && hbp->zee != original_zee){ + if(hbp == curbuf){ + if(screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED) == FAIL){ + error_status = FAIL; + }/* End IF */ + }/* End IF */ + hbp->ismodified = YES; + }/* End IF */ + + checkpoint_modified = YES; + + return(error_status); + +}/* End Routine */ + + + +/* BUFF_WRITE - Write the specified buffer out to a file + * + * Function: + * + * This routine is generally called on an EW command when the user + * wishes to write out the contents of the buffer. + */ +int +buff_write(hbp,chan,start,end) +struct buff_header *hbp; +int chan; +int start; +int end; +{ +register int bcount; +register struct buff_line *lbp; +register char *cp1,*cp2; +register int line_bcount; +char iobuf[IO_BUFFER_SIZE]; +int status; + + PREAMBLE(); + + bcount = 0; + cp2 = iobuf; + + lbp = buff_find_line(hbp,start); + line_bcount = buff_find_offset(hbp,lbp,start); + cp1 = lbp->buffer + line_bcount; + line_bcount = lbp->byte_count - line_bcount; + + while(start < end){ + if(bcount == sizeof(iobuf)){ + status = write(chan,iobuf,sizeof(iobuf)); + if(status != sizeof(iobuf)){ + return(FAIL); + }/* End IF */ + bcount = 0; + cp2 = iobuf; + }/* End IF */ + + if(line_bcount == 0){ + lbp = lbp->next_line; + cp1 = lbp->buffer; + line_bcount = lbp->byte_count; + }/* End IF */ + + *cp2++ = *cp1++; + bcount += 1; + line_bcount -= 1; + start += 1; + + }/* End While */ + + if(bcount > 0){ + status = write(chan,iobuf,(unsigned)bcount); + if(status != bcount){ + return(FAIL); + }/* End IF */ + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_SWITCH - Switch the current edit buffer + * + * Function: + * + * This routine is called when we wish to switch the current buffer + * to some other edit buffer. + */ +int +buff_switch(hbp,map_flag) +struct buff_header *hbp; +int map_flag; +{ +char tmp_message[LINE_BUFFER_SIZE]; +char *cp; + + PREAMBLE(); + + curbuf = curwin->win_buffer = hbp; + + sprintf(tmp_message," %s",hbp->buffer_number,hbp->name); + screen_label_line(hbp,tmp_message,LABEL_C_FILENAME); + + cp = ""; + if(hbp->isreadonly) cp = "(READONLY)"; + screen_label_line(hbp,cp,LABEL_C_READONLY); + + cp = ""; + if(hbp->ismodified) cp = "(modified)"; + screen_label_line(hbp,cp,LABEL_C_MODIFIED); + + if(hbp->buffer_number == 0 && map_flag){ + buff_buffer_map(); + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_BUFFER_MAP - Build a map of the existing buffers. + * + * Function: + * + * This routine is called when the buffer map buffer is entered. It is + * used to fill this routine with the appropriate text. + */ +void +buff_buffer_map() +{ +register struct buff_header *hbp; +char tmp_buffer[LINE_BUFFER_SIZE],padd_buffer[LINE_BUFFER_SIZE]; +register int i; +int count; +register char *cp; +int max_length; + + PREAMBLE(); + + buff_delete(curbuf,0,curbuf->zee); + + for(i = 0; i < sizeof(padd_buffer); i++){ + padd_buffer[i] = ' '; + }/* End FOR */ + padd_buffer[sizeof(padd_buffer)-1] = '\0'; + + max_length = 0; + for(hbp = buffer_headers; hbp != NULL ; hbp = hbp->next_header){ + if(hbp->buffer_number == 0) continue; + i = strlen(hbp->name); + if(i > max_length) max_length = i; + }/* End FOR */ + + for(hbp = buffer_headers; hbp != NULL ; hbp = hbp->next_header){ + if(hbp->buffer_number <= 0) continue; + i = max_length - strlen(hbp->name); + sprintf(tmp_buffer," %s%s %s %6d bytes\n", + hbp->buffer_number,hbp->name,&padd_buffer[sizeof(padd_buffer)-1-i], + hbp->ismodified ? "(modified)" : " ",hbp->zee); + i = strlen(tmp_buffer); + buff_insert(curbuf,curbuf->dot,tmp_buffer,i); + }/* End FOR */ + + buff_insert(curbuf,curbuf->dot,"\n",1); + + for(hbp = buffer_headers; hbp != NULL ; hbp = hbp->next_header){ + if(hbp->buffer_number >= 0) continue; + i = max_length - strlen(hbp->name); + sprintf(tmp_buffer," %s%s %s %6d bytes\n", + hbp->buffer_number,hbp->name,&padd_buffer[sizeof(padd_buffer)-1-i], + hbp->ismodified ? "(modified)" : " ",hbp->zee); + i = strlen(tmp_buffer); + buff_insert(curbuf,curbuf->dot,tmp_buffer,i); + }/* End FOR */ + +/* + * Here to tell him if any q registers are currently on the push down + * list. + */ + i = count = 0; + for(hbp = qregister_push_down_list; hbp != NULL ; hbp = hbp->next_header){ + i += 1; + count += hbp->zee; + }/* End FOR */ + + if(i > 0){ + sprintf(tmp_buffer, + "\nQ register push down list contains %d register%s, %d bytes\n", + i,(i > 1)?"s":"",count); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + }/* End IF */ + +/* + * Insert our TITLE + */ + max_length = term_columns <= 80 ? term_columns : 80; + + buff_insert(curbuf,curbuf->dot,"\n\n",2); + (void) strcpy(tmp_buffer,"Video TECO"); + i = (max_length - strlen(tmp_buffer))/2; + if(i < 0) i = 0; + (void) strcat(tmp_buffer,"\n"); + buff_insert(curbuf,curbuf->dot,padd_buffer,i); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + (void) strcpy(tmp_buffer," by Paul Cantrell & Joyce Nishinaga"); + i = (max_length - strlen(tmp_buffer))/2; + if(i < 0) i = 0; + (void) strcat(tmp_buffer,"\n"); + buff_insert(curbuf,curbuf->dot,padd_buffer,i); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + +#if 0 + (void) strcpy(tmp_buffer,id_string); + if(tmp_buffer[0] == '%'){ + tmp_buffer[0] = '\0'; + cp = rcs_id; + i = 0; + if(*cp == '$'){ + cp++; + while(cp[i] == "Revision: "[i]) i += 1; + cp += i; + }/* End IF */ + i = 0; + while(*cp != '$' && *cp != '\0'){ + tmp_buffer[i++] = *cp++; + }/* End While */ + tmp_buffer[i] = '\0'; + if(i <= 2) sprintf(tmp_buffer,"*%d.%d* ",VMAJOR,VMINOR); + + cp = rcs_date; + i = 0; + if(*cp == '$'){ + cp++; + while(cp[i] == "Date: "[i]) i += 1; + cp += i; + }/* End IF */ + i = strlen(tmp_buffer); + while(*cp != '$' && *cp != '\0'){ + tmp_buffer[i++] = *cp++; + }/* End While */ + tmp_buffer[i] = '\0'; + + }/* End IF */ +#endif + sprintf(tmp_buffer,"*%d.%d* ",VMAJOR,VMINOR); + cp = rcs_date; + i = 0; + if(*cp == '$'){ + cp++; + while(cp[i] == "Date: "[i]) i += 1; + cp += i; + }/* End IF */ + i = strlen(tmp_buffer); + while(*cp != '$' && *cp != '\0'){ + tmp_buffer[i++] = *cp++; + }/* End While */ + tmp_buffer[i] = '\0'; + + i = (max_length - strlen(tmp_buffer))/2; + if(i < 0) i = 0; + (void) strcat(tmp_buffer,"\n"); + buff_insert(curbuf,curbuf->dot,padd_buffer,i); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + tecmem_stats(); + + curbuf->dot = 0; + return; + +}/* End Routine */ + + + +/* BUFF_INSERT - Insert a string at current position + * + * Function: + * + * This routine is called to insert a string into the specified buffer + * at the buffer's current location. + */ +int +buff_insert(hbp,position,buffer,length) +struct buff_header *hbp; +register int position; +register char *buffer; +register int length; +{ +struct buff_header fake_header; +struct buff_line *fake_line; +register char *cp; + + PREAMBLE(); + + fake_line = + (struct buff_line *)tec_alloc(TYPE_C_LINE,sizeof(struct buff_line)); + + if(fake_line == NULL) return(FAIL); + + fake_header.dot = 0; + fake_header.zee = 0; + fake_header.pos_cache.lbp = fake_line; + fake_header.pos_cache.base = 0; + fake_header.first_line = fake_line; + + fake_line->buffer_size = LINE_BUFFER_SIZE; + fake_line->buffer = buffer; + fake_line->next_line = NULL; + fake_line->prev_line = NULL; + fake_line->format_line = NULL; + + while(length){ + cp = fake_line->buffer; + fake_line->byte_count = 0; + while(1){ + fake_line->byte_count += 1; + length -= 1; + if(*cp == '\n' || length == 0){ + buff_insert_from_buffer_with_undo( + NULL, + hbp, + position, + &fake_header, + 0, + fake_line->byte_count + ); +/* + * The following is a terrible kludge. The buff_find_line code ends up + * inserting a null line buffer after our fake line, in order to + * represent the location following the last character. If we don't + * free it, it will get lost and consume memory... + */ + if(fake_line->next_line){ + buff_free_line_buffer(fake_line->next_line); + fake_line->next_line = NULL; + }/* End IF */ + fake_line->buffer += fake_line->byte_count; + position += fake_line->byte_count; + break; + }/* End IF */ + cp++; + }/* End While */ + }/* End While */ + + tec_release(TYPE_C_LINE,(char *)fake_line); + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_INSERT_FROM_BUFFER_WITH_UNDO - Copy bytes from another buffer + * + * Function: + * + * This routine copies bytes from one buffer to another, arranging + * undo capability so that we can reverse the process. Because it + * knows the lengths of each line, this should be a very efficient + * method of inserting data, and should certainly have preference + * over using the byte-at-a-time routines. + */ +int +buff_insert_from_buffer_with_undo(ct,dbp,dest_position,sbp,src_position,length) +struct cmd_token *ct; +struct buff_header *dbp; +int dest_position; +struct buff_header *sbp; +int src_position; +int length; +{ +register int i; +struct undo_token *ut = 0; +struct buff_line *dlbp,*sb_lbp,*se_lbp,*nlbp; +int doff,sb_off,se_off; +int newline_included_in_src_data; +int bytes_to_copy_from_source; +int bytes_required_for_line,bytes_to_allocate; +char *new_buffer; +register char *dcp,*scp; +int bytes_inserted_so_far = 0; + +#ifdef DEBUG +char outbuf[1024]; +char inbuf[10]; +extern int tty_input_chan; + term_goto(0,0); + term_clrtobot(); + term_flush(); +#endif /* DEBUG */ + + PREAMBLE(); +/* + * Insure that the specified position is legal + */ + if(src_position < 0 || src_position > sbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf( + tmp_message, + "buff_insert_from_buffer_with_undo: bad src pos %d %s Z = %d\n", + src_position, + sbp->name,sbp->zee + ); + tec_panic(tmp_message); + }/* End IF */ + if(dest_position < 0 || dest_position > dbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf( + tmp_message, + "buff_insert_from_buffer_with_undo: bad dest pos %d %s Z = %d\n", + dest_position, + dbp->name,dbp->zee + ); + tec_panic(tmp_message); + }/* End IF */ + + if(ct){ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + }/* End IF */ + +/* + * Determine which line the current insert point is on by calling the + * find routines. + */ + dlbp = buff_find_line(dbp,dest_position); + doff = buff_find_offset(dbp,dlbp,dest_position); + + sb_lbp = buff_find_line(sbp,src_position); + sb_off = buff_find_offset(sbp,sb_lbp,src_position); + + se_lbp = buff_find_line(sbp,src_position+length); + se_off = buff_find_offset(sbp,se_lbp,src_position+length); + +#ifdef DEBUG + movc3(dlbp->buffer,outbuf,dlbp->byte_count); + outbuf[dlbp->byte_count] = '\0'; + if(outbuf[dlbp->byte_count-1] == '\n') outbuf[dlbp->byte_count-1] = '\0'; + fprintf(stderr,"'%s' dest begin doff %d\n",outbuf,doff); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[doff+1] = '^'; outbuf[doff+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); + + movc3(sb_lbp->buffer,outbuf,sb_lbp->byte_count); + outbuf[sb_lbp->byte_count] = '\0'; + if(outbuf[sb_lbp->byte_count-1] == '\n'){ + outbuf[sb_lbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' source begin sb_off %d\n",outbuf,sb_off); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[sb_off+1] = '^'; outbuf[sb_off+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); + + movc3(se_lbp->buffer,outbuf,se_lbp->byte_count); + outbuf[se_lbp->byte_count] = '\0'; + if(outbuf[se_lbp->byte_count-1] == '\n'){ + outbuf[se_lbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' source end se_off %d\n",outbuf,se_off); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[se_off+1] = '^'; outbuf[se_off+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); +#endif /* DEBUG */ + +/* + * If the buffer just became modified for the first time, we need to change the + * label line to reflect this. + */ + if(dbp->ismodified == NO){ + if(dbp == curbuf){ + screen_label_line(dbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + dbp->ismodified = YES; + }/* End IF */ + + checkpoint_modified = YES; + +/* + * Since we are modifying the destination line buffer structure, we + * want to delete any format structures so that the screen display + * code will know to reformat it. + */ + if(dlbp->format_line){ + screen_free_format_lines(dlbp->format_line); + dlbp->format_line = NULL; + }/* End IF */ +/* + * The cases we have to handle are much more complex if there are newlines + * being inserted, so we determine now whether this is the case or not. + */ + newline_included_in_src_data = 1; + if(sb_lbp->buffer[sb_lbp->byte_count-1] != '\n'){ + newline_included_in_src_data = 0; + }/* End IF */ + +#ifdef DEBUG + fprintf( + stderr, + "newline_included_in_src_data = %d\n", + newline_included_in_src_data + ); +#endif /* DEBUG */ + + bytes_to_copy_from_source = sb_lbp->byte_count - sb_off; + if(bytes_to_copy_from_source > length){ +#ifdef DEBUG + fprintf( + stderr, + "bytes_to_copy_from_source %d > length %d\n", + bytes_to_copy_from_source, + length + ); +#endif /* DEBUG */ + bytes_to_copy_from_source = length; + newline_included_in_src_data = 0; + }/* End IF */ +#ifdef DEBUG + fprintf( + stderr, + "bytes_to_copy_from_source is now %d\n", + bytes_to_copy_from_source + ); +#endif /* DEBUG */ + +/* + * Test for a simple case - one in which there are no newlines in the source + * data. In this case, we know that we simply have to insert it into the + * current line buffer. If the buffer area is not big enough, we may have + * to allocate a new buffer. + */ + if(newline_included_in_src_data == 0){ +/* + * The total line length will be the current line length, plus the amount + * of data being inserted. + */ + bytes_required_for_line = dlbp->byte_count + bytes_to_copy_from_source; + +#ifdef DEBUG + fprintf( + stderr, + "no newline, bytes_required_for_line %d \n", + bytes_required_for_line + ); +#endif /* DEBUG */ + +/* + * If the current buffer is too small, we allocate a new buffer and copy + * all the data into it, and then free the old buffer. + */ + if(bytes_required_for_line > dlbp->buffer_size){ +#ifdef DEBUG + fprintf( + stderr, + "new allocation required\n" + ); +#endif /* DEBUG */ + bytes_to_allocate = + bytes_required_for_line + INCREMENTAL_LINE_BUFFER_SIZE - 1; + bytes_to_allocate -= + bytes_to_allocate % INCREMENTAL_LINE_BUFFER_SIZE; + if(dlbp->buffer_size > LARGEST_LOOKASIDE_BLOCK){ + bytes_to_allocate = dlbp->buffer_size * 2; + }/* End IF */ + + if(bytes_to_allocate < bytes_required_for_line){ + char tmp_message[1024]; + sprintf( + tmp_message, + "buff_insert_from_buffer_with_undo need %d alloced %d\n", + bytes_required_for_line, + bytes_to_allocate + ); + tec_panic(tmp_message); + }/* End IF */ + + new_buffer = + tec_alloc( + TYPE_C_LINEBUF, + bytes_to_allocate + ); + + if(new_buffer == NULL){ + return(FAIL); + }/* End IF */ + + dcp = new_buffer; +/* + * Copy up to the current insertion point in the new buffer, then the + * bytes coming from the source line, then if the source line didn't + * contribute a newline, the rest of the bytes coming from the original + * destination line. + */ + movc3(dlbp->buffer,dcp,doff); + dcp += doff; + movc3(sb_lbp->buffer+sb_off,dcp,bytes_to_copy_from_source); + dcp += bytes_to_copy_from_source; + movc3(&dlbp->buffer[doff],dcp,dlbp->byte_count-doff); + bytes_inserted_so_far += bytes_to_copy_from_source; + + tec_release(TYPE_C_LINEBUF,dlbp->buffer); + dlbp->buffer = new_buffer; + dlbp->buffer_size = bytes_to_allocate; + dlbp->byte_count = bytes_required_for_line; + }/* End IF */ +/* + * If the current buffer is large enough, make a hole to insert the new + * data into, and copy in the new data. + */ + else { +#ifdef DEBUG + fprintf( + stderr, + "it will fit into the current size of %d\n", + dlbp->buffer_size + ); +#endif /* DEBUG */ + dcp = &dlbp->buffer[bytes_required_for_line-1]; + scp = &dlbp->buffer[dlbp->byte_count-1]; + for(i = 0; i < dlbp->byte_count - doff; i++){ + *dcp-- = *scp--; + }/* End FOR */ + movc3( + &sb_lbp->buffer[sb_off], + &dlbp->buffer[doff], + bytes_to_copy_from_source + ); + dlbp->byte_count = bytes_required_for_line; + bytes_inserted_so_far += bytes_to_copy_from_source; + }/* End Else */ + +#ifdef DEBUG + doff += bytes_to_copy_from_source; + movc3(dlbp->buffer,outbuf,dlbp->byte_count); + outbuf[dlbp->byte_count] = '\0'; + if(outbuf[dlbp->byte_count-1] == '\n'){ + outbuf[dlbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' new dest doff %d\n",outbuf,doff); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[doff+1] = '^'; outbuf[doff+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); +#endif /* DEBUG */ + }/* End IF */ + +/* + * Else, if there is a newline involved, things get more complicated... + * The first thing we do is construct a line buffer which holds any + * trailing part of the source data (i.e. not terminated by a newline) + * along with the trailing part of the line we are inserting into. + */ + else { + bytes_required_for_line = dlbp->byte_count - doff + se_off; + if(bytes_required_for_line > 0){ + nlbp = allocate_line_buffer(bytes_required_for_line); + if(nlbp == NULL) return(FAIL); + + nlbp->next_line = dlbp->next_line; + dlbp->next_line = nlbp; + if(nlbp->next_line) nlbp->next_line->prev_line = nlbp; + nlbp->prev_line = dlbp; + movc3(se_lbp->buffer,nlbp->buffer,se_off); + movc3( + &dlbp->buffer[doff], + &nlbp->buffer[se_off], + dlbp->byte_count-doff + ); + nlbp->byte_count = bytes_required_for_line; + dlbp->byte_count -= ( dlbp->byte_count - doff ); + bytes_inserted_so_far += se_off; + }/* End IF */ +/* + * Now we want to merge the data from the begining of the original + * destinatio line, and the data up to the first newline from the + * source data. + */ + bytes_required_for_line = + dlbp->byte_count + sb_lbp->byte_count - sb_off; + if(bytes_required_for_line > dlbp->buffer_size){ + bytes_to_allocate = + bytes_required_for_line + INCREMENTAL_LINE_BUFFER_SIZE - 1; + bytes_to_allocate -= + bytes_to_allocate % INCREMENTAL_LINE_BUFFER_SIZE; + if(dlbp->buffer_size > LARGEST_LOOKASIDE_BLOCK){ + bytes_to_allocate = dlbp->buffer_size * 2; + }/* End IF */ + + if(bytes_to_allocate < bytes_required_for_line){ + char tmp_message[1024]; + sprintf( + tmp_message, + "buff_insert_from_buffer_with_undo needs %d alloced %d\n", + bytes_required_for_line, + bytes_to_allocate + ); + tec_panic(tmp_message); + }/* End IF */ + + new_buffer = + tec_alloc( + TYPE_C_LINEBUF, + bytes_to_allocate + ); + if(new_buffer != NULL){ + dcp = new_buffer; + movc3(dlbp->buffer,dcp,dlbp->byte_count); + dcp += dlbp->byte_count; + movc3(&sb_lbp->buffer[sb_off],dcp,sb_lbp->byte_count - sb_off); + tec_release(TYPE_C_LINEBUF,dlbp->buffer); + dlbp->buffer = new_buffer; + dlbp->buffer_size = bytes_to_allocate; + dlbp->byte_count = bytes_required_for_line; + bytes_inserted_so_far += (sb_lbp->byte_count - sb_off); + }/* End IF */ + }/* End IF */ + + else { + movc3( + &sb_lbp->buffer[sb_off], + &dlbp->buffer[doff], + sb_lbp->byte_count - sb_off + ); + dlbp->byte_count = bytes_required_for_line; + bytes_inserted_so_far += (sb_lbp->byte_count - sb_off); + }/* End Else */ + +/* + * Now we want to copy all the full-length lines in between the starting + * and ending line buffers. + */ + sb_lbp = sb_lbp->next_line; + while(sb_lbp){ + if(sb_lbp == se_lbp) break; + bytes_required_for_line = sb_lbp->byte_count; + nlbp = allocate_line_buffer(bytes_required_for_line); + if(nlbp == NULL) break; + nlbp->next_line = dlbp->next_line; + dlbp->next_line = nlbp; + if(nlbp->next_line) nlbp->next_line->prev_line = nlbp; + nlbp->prev_line = dlbp; + movc3(sb_lbp->buffer,nlbp->buffer,bytes_required_for_line); + nlbp->byte_count = bytes_required_for_line; + bytes_inserted_so_far += bytes_required_for_line; + dlbp = nlbp; + sb_lbp = sb_lbp->next_line; + }/* End While */ + +#ifdef DEBUG + movc3(dlbp->buffer,outbuf,dlbp->byte_count); + outbuf[dlbp->byte_count] = '\0'; + if(outbuf[dlbp->byte_count-1] == '\n'){ + outbuf[dlbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' new dest doff %d\n",outbuf,doff); + for(i = 0; i < sizeof(outbuf); i++) outbuf[i] = ' '; + outbuf[doff+1] = '^'; outbuf[doff+2] = '\0'; + fprintf(stderr,"%s\n",outbuf); + + movc3(nlbp->buffer,outbuf,nlbp->byte_count); + outbuf[nlbp->byte_count] = '\0'; + if(outbuf[nlbp->byte_count-1] == '\n'){ + outbuf[nlbp->byte_count-1] = '\0'; + } + fprintf(stderr,"'%s' new line\n",outbuf); +#endif /* DEBUG */ + }/* End Else */ + + + if(ct){ + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)dbp; + ut->iarg1 = dest_position; + ut->iarg2 = bytes_inserted_so_far; + }/* End IF */ + +/* + * Depending on whether or not the insert position precedes dot or not, we + * may have to increment dot. + */ + if(dest_position <= dbp->dot) dbp->dot += bytes_inserted_so_far; + dbp->zee += bytes_inserted_so_far; +/* + * If we are inserting characters before the position cache, it will + * shift our cached position down by the length, so we adjust the cache. + */ + if(dest_position < dbp->pos_cache.base){ + dbp->pos_cache.base += bytes_inserted_so_far; + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + +/* BUFF_INSERT_WITH_UNDO - Insert a string and arrange for undo capability + * + * Function: + * + * This routine is called when characters need to be inserted, and + * also need to be un-inserted if the command is undone. + */ +int +buff_insert_with_undo(ct,hbp,position,buffer,length) +struct cmd_token *ct; +struct buff_header *hbp; +register int position; +register char *buffer; +register int length; +{ +struct undo_token *ut; + + PREAMBLE(); + + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)hbp; + ut->iarg1 = position; + ut->iarg2 = length; + + if(buff_insert(hbp,position,buffer,length) == FAIL){ + ut->iarg2 = 0; + return(FAIL); + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_INSERT_CHAR - Insert the specified character into the current position + * + * Function: + * + * This routine is called to insert the single character into the buffer + * at the current position (dot). It has to worry about such things as + * noting that the buffer has been modified, the screen display may be + * invalid, etc. + */ +int +buff_insert_char(hbp,position,data) +register struct buff_header *hbp; +int position; +char data; +{ +register struct buff_line *lbp; +struct buff_line *nlbp; +register char *cp,*ocp; +register int i,j; + + PREAMBLE(); + +/* + * Insure that the specified position is legal + */ + if(position > hbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"buff_insert_char: bad position %d %s Z = %d\n", + position,hbp->name,hbp->zee); + tec_panic(tmp_message); + }/* End IF */ +/* + * Determine which line the current insert point is on by calling the + * find routines. + */ + lbp = buff_find_line(hbp,position); + i = buff_find_offset(hbp,lbp,position); +/* + * Depending on whether or not the specified position precedes dot or not, we + * may have to decrement dot. + */ + if(position <= hbp->dot) hbp->dot += 1; + hbp->zee += 1; +/* + * If the buffer just became modified for the first time, we need to change the + * label line to reflect this. + */ + if(hbp->ismodified == NO){ + if(hbp == curbuf){ + screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + hbp->ismodified = YES; + }/* End IF */ + + checkpoint_modified = YES; + +/* + * If we are inserting a character before the position cache, it will + * shift our cached position down by one, so we adjust the cache. + */ + if(position < hbp->pos_cache.base){ + hbp->pos_cache.base += 1; + }/* End IF */ +/* + * If there is a format_line structure associated with this line, we make it + * go away so that the screen display routine will rebuild the display contents + * of this line. + */ + if(lbp->format_line){ + screen_free_format_lines(lbp->format_line); + lbp->format_line = NULL; + }/* End IF */ + +/* + * If this is a newline, we do things a little differently because this will + * actually cause the line to be broken up. In this case we actually have to + * create a new line buffer structure. + */ + if(data == '\n'){ +/* + * Calculate how many bytes are to move to the new line + */ + j = lbp->byte_count - i; + if(j > 0){ + nlbp = allocate_line_buffer(j); + if(nlbp == NULL) return(FAIL); + nlbp->next_line = lbp->next_line; + lbp->next_line = nlbp; + if(nlbp->next_line) nlbp->next_line->prev_line = nlbp; + nlbp->prev_line = lbp; + +/* + * Now copy the data into the new line buffer + */ + cp = lbp->buffer + i; + ocp = nlbp->buffer; + + while(nlbp->byte_count < j){ + *ocp++ = *cp++; + nlbp->byte_count++; + lbp->byte_count--; + }/* End While */ + }/* End IF */ + }/* End IF */ + +/* + * If this line buffer does not have room for the data, we need to expand it + */ + if(lbp->byte_count == lbp->buffer_size){ + ocp = + tec_alloc( + TYPE_C_LINEBUF, + lbp->buffer_size + INCREMENTAL_LINE_BUFFER_SIZE + ); + if(ocp == NULL) return(FAIL); + movc3(lbp->buffer,ocp,lbp->buffer_size); + tec_release(TYPE_C_LINEBUF,lbp->buffer); + lbp->buffer = ocp; + lbp->buffer_size += INCREMENTAL_LINE_BUFFER_SIZE; + }/* End IF */ + + +/* + * Make a hole in which to place the byte. + */ + if(lbp->byte_count){ + ocp = lbp->buffer + lbp->byte_count; + cp = ocp - 1; + j = lbp->byte_count - i; + while(j--){ + *ocp-- = *cp--; + }/* End While */ + }/* End IF */ + + cp = lbp->buffer + i; +/* + * Ok, now insert the data into the hole + */ + *cp = data; + lbp->byte_count += 1; + + return(SUCCESS); + +}/* End Routine */ + +/* BUFF_INSERT_CHAR_WITH_UNDO - Insert a single char with undo capability + * + * Function: + * + * This is an envelope routine which guarentees that the character + * to be input can be undone if necessary. + */ +int +buff_insert_char_with_undo(ct,hbp,position,data) +struct cmd_token *ct; +register struct buff_header *hbp; +int position; +char data; +{ +struct undo_token *ut; + + PREAMBLE(); + + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)hbp; + ut->iarg1 = position; + ut->iarg2 = 1; + + if(buff_insert_char(hbp,position,data) == FAIL){ + ut->iarg2 = 0; + return(FAIL); + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_DELETE - Delete specified number of characters + * + * Function: + * + * This routine is called to delete the specified number of characters + * at the specified location in the buffer. + */ +void +buff_delete(hbp,position,count) +struct buff_header *hbp; +int position; +register int count; +{ + + PREAMBLE(); +/* + * Insure that there are that many character in the buffer after the specified + * position. + */ + if((position + count) > hbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"buff_delete: illegal range %d-%d %s Z = %d\n", + position,position+count,hbp->name,hbp->zee); + tec_panic(tmp_message); + }/* End IF */ +/* + * For now we are cheap and just repeatedly call the single character delete + * routine, because this is simple. It is also very expensive and will have to + * be fixed eventually. + */ + while(count){ + buff_delete_char(hbp,position); + count -= 1; + }/* End While */ + +}/* End Routine */ + + + +/* BUFF_DELETE_CHAR - Delete the character at the current position + * + * Function: + * + * This routine is called to delete the single character which is at + * the current buffer position. + */ +int +buff_delete_char(hbp,position) +register struct buff_header *hbp; +int position; +{ +register struct buff_line *lbp; +struct buff_line *nlbp; +register char *cp,*ocp; +register int i,j; + + PREAMBLE(); +/* + * Insure that the specified position is legal + */ + if(position >= hbp->zee){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"buff_delete_char: bad position %d %s Z = %d\n", + position,hbp->name,hbp->zee); + tec_panic(tmp_message); + }/* End IF */ +/* + * Call buff_find_line to find the line structure that the specified + * position resides on. + */ + lbp = buff_find_line(hbp,position); + i = buff_find_offset(hbp,lbp,position); +/* + * If there is a format_line structure associated with this line, we make it + * go away so that the screen display routine will rebuild the display contents + * of this line. + */ + if(lbp->format_line){ + screen_free_format_lines(lbp->format_line); + lbp->format_line = NULL; + }/* End IF */ +/* + * Get a pointer to the character that is about to be deleted + */ + cp = lbp->buffer + i; +/* + * Depending on whether or not the specified position precedes dot or not, we + * may have to decrement dot. + */ + if(position < hbp->dot) hbp->dot -= 1; + hbp->zee -= 1; +/* + * If this has modified the buffer for the first time, it needs to be displayed + * in the label line. + */ + if(hbp->ismodified == NO){ + hbp->ismodified = YES; + if(hbp == curbuf){ + screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + }/* End IF */ + + checkpoint_modified = YES; + +/* + * If we are deleting a character before the position cache, it will + * shift our cached position up by one, so we adjust the cache. + */ + if(position < hbp->pos_cache.base){ + hbp->pos_cache.base -= 1; + }/* End IF */ +/* + * If this is not a newline, then things are pretty simple. Just + * make the character go away... + */ + if(*cp != '\n'){ + movc3(cp+1,cp,lbp->byte_count-i-1); + lbp->byte_count -= 1; + return(SUCCESS); + }/* End IF */ + +/* + * If this is a newline, we do things a little differently because this + * actually causes the two lines to become concatenated (unless this is the + * final line in the buffer). If the buffer area is not big enough to hold + * both lines, we have to create a bigger area. + */ + nlbp = lbp->next_line; + if(nlbp == NULL){ + lbp->byte_count -= 1; + return(SUCCESS); + }/* End IF */ + +/* + * If there is a format_line structure associated with the second line, we make + * it go away since this line is about to be trashed. + */ + if(nlbp->format_line){ + screen_free_format_lines(nlbp->format_line); + nlbp->format_line = NULL; + }/* End IF */ + +/* + * Test whether or not the buffer is big enough to hold the data from both + * lines. + */ + j = lbp->byte_count + nlbp->byte_count - 1; + if(lbp->buffer_size < j){ + i = j + INCREMENTAL_LINE_BUFFER_SIZE - 1; + i -= i % INCREMENTAL_LINE_BUFFER_SIZE; + cp = tec_alloc(TYPE_C_LINEBUF,i); + if(cp == NULL) return(FAIL); + movc3(lbp->buffer,cp,lbp->buffer_size); + tec_release(TYPE_C_LINEBUF,lbp->buffer); + lbp->buffer = cp; + lbp->buffer_size = i; + }/* End IF */ + +/* + * Ok, now concatenate the two line buffers by copying the second line + * into the first. Because we copy on top of the newline, it effectively + * goes away, so we have to decrement the line count by one. + */ + lbp->byte_count -= 1; + cp = lbp->buffer + lbp->byte_count; + ocp = nlbp->buffer; + i = nlbp->byte_count; + while(i--){ + *cp++ = *ocp++; + lbp->byte_count++; + }/* End While */ + +/* + * Fixup the next and previous pointers so that they point around the + * line that we are about to remove. + */ + lbp->next_line = nlbp->next_line; + if(nlbp->next_line) nlbp->next_line->prev_line = lbp; + + if(hbp->pos_cache.lbp == nlbp) hbp->pos_cache.lbp = NULL; + buff_free_line_buffer(nlbp); + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_DELETE_WITH_UNDO - Clobber buffer positions and build undo list + * + * Function: + * + * This routine is called when we want to delete a range of bytes in the + * buffer, and build an undo list so that we could replace them if we need + * to. This happens to be pretty efficient for a bulk delete since we can + * just move the line buffers over to the undo list. + */ +int +buff_delete_with_undo(ct,hbp,position,count) +struct cmd_token *ct; +struct buff_header *hbp; +int position; +int count; +{ +register char *cp; +struct undo_token *ut; +register struct buff_line *lbp; +struct buff_line *top_lbp; +int top_offset,local_count,bulk_position; +int bytes_deleted_so_far = 0; + + PREAMBLE(); + +/* ??? + * I think I may need this to insure that dot doesn't get changed. For + * instance, if we had abc and deleted 'a', we would have bc. + * Then if we undo, it's an insert of 'a' at position zero which causes + * dot to shift so we would now have abc. + */ + if(hbp->dot == (position+count)){ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + }/* End IF */ + +/* + * Because some of the undo functions only work in the 'current' buffer, if + * the user is deleting from a buffer other than the 'current' one, we have + * to set up some undo tokens to switch the current buffer back and forth + * during the undo process, should it occur. + */ + if(hbp != curbuf){ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = curbuf->buffer_number; + }/* End IF */ + + top_lbp = buff_find_line(hbp,position); + top_offset = buff_find_offset(hbp,top_lbp,position); + +/* + * First step will be to delete the (hopefully) large block of line buffer + * structures in the middle. This will leave a partial line at the top and + * also at the bottom. To start, if the begining is in the middle of a line, + * we need to skip over the bytes on that line. + */ + local_count = count; + lbp = top_lbp; + bulk_position = position; + if(top_offset){ + local_count -= lbp->byte_count - top_offset; + bulk_position = position + lbp->byte_count - top_offset; + lbp = lbp->next_line; + }/* End IF */ + if(lbp) top_lbp = lbp->prev_line; + +/* + * Ok, now we want to chain over all the line buffer structures which can be + * removed in bulk. lbp gets left pointing at the final line which can be bulk + * removed. + */ + if(lbp && lbp->byte_count <= local_count){ +/* + * We will get to bulk delete at least one line, so allocate an undo buffer. + * Point the undo token at the head of the list of line buffers. Remember + * the buffer position of the first byte of bulk data in iarg1, and keep a + * running count of the number of bytes of bulk data in iarg2. + */ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_BULK_INSERT; + ut->carg1 = (char *)lbp; + ut->iarg1 = bulk_position; + ut->iarg2 = 0; +/* + * Now scan down until we reach the bottom of the bulk region. Keep track of + * how many characters this all represents, and clean up any associated format + * line structures. + */ + while(1){ + ut->iarg2 += lbp->byte_count; + local_count -= lbp->byte_count; + if(lbp->format_line){ + screen_free_format_lines(lbp->format_line); + lbp->format_line = NULL; + }/* End IF */ + if(lbp->next_line == NULL) break; + if(lbp->next_line->byte_count > local_count) break; + lbp = lbp->next_line; + }/* End While */ +/* + * Unchain the bulk area from the rest of the buffer. First, make the parent + * structure's forward pointer point to the next line buffer after the final + * one in the bulk deletion region. + */ + if(top_lbp) top_lbp->next_line = lbp->next_line; + else hbp->first_line = lbp->next_line; + if(hbp->first_line == NULL) hbp->first_line = allocate_line_buffer(1); +/* + * Now we have to fixup the backwards pointer of the trailing line buffer + * structure, if it exists. + */ + if(lbp->next_line){ + lbp->next_line->prev_line = top_lbp; + }/* End IF */ +/* + * Finally, clobber the 'next' pointer in lbp which is the end of the bulk + * area. + */ + lbp->next_line = NULL; +/* + * Update the remaining count of bytes to be deleted. + */ + bytes_deleted_so_far += ut->iarg2; +/* + * Depending on whether or not the bulk position precedes dot or not, we + * may have to decrement dot. In any case, we have to update zee. + */ + if(bulk_position < hbp->dot){ + if(hbp->dot > bulk_position + ut->iarg2) hbp->dot -= ut->iarg2; + else hbp->dot = bulk_position; + }/* End IF */ + hbp->zee -= ut->iarg2; +/* + * If this has modified the buffer for the first time, it needs to be displayed + * in the label line. + */ + if(hbp->ismodified == NO){ + hbp->ismodified = YES; + if(hbp == curbuf){ + screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + }/* End IF */ + + checkpoint_modified = YES; + +/* + * If we are deleting from before the position cache, it will invalidate the + * information there, so we have to set it invalid so it does not get used. + */ + if(hbp->pos_cache.base >= bulk_position){ + hbp->pos_cache.lbp = NULL; + }/* End IF */ + + }/* End IF */ + +/* + * Now, just brute force clobber the remaining few bytes + */ + if(bytes_deleted_so_far < count){ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_MEMFREE; + ut->carg1 = cp = tec_alloc(TYPE_C_CBUFF,count-bytes_deleted_so_far); + if(cp != NULL){ +/* + * Set up the undo token to put the text back into the q-register, and copy + * it into our save buffer as we delete it from the q-register. + */ + ut = allocate_undo_token(ct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_INSERT; + ut->iarg1 = position; + ut->iarg2 = 0; + ut->carg1 = cp; + while(count > bytes_deleted_so_far){ + *cp++ = buff_contents(hbp,position); + if(buff_delete_char(hbp,position) == FAIL) return(FAIL); + bytes_deleted_so_far += 1; + ut->iarg2 += 1; + }/* End While */ + }/* End IF */ + }/* End IF */ + +/* + * Insure that the undo commands are working on the buffer the user specified + */ + if(hbp != curbuf){ + ut = allocate_undo_token(ct); + if(ut == FAIL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = hbp->buffer_number; + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* BUFF_BULK_INSERT - Insert a list of line buffer structures + * + * Function: + * + * This routine is used to insert a list of line buffer structures at + * the specified position in the buffer. + */ +void +buff_bulk_insert(hbp,position,count,lbp) +struct buff_header *hbp; +int position; +int count; +register struct buff_line *lbp; +{ +register struct buff_line *olbp; +int offset; + + PREAMBLE(); +/* + * If this will modify the buffer for the first time, it needs to be displayed + * in the label line. + */ + if(hbp->ismodified == NO){ + hbp->ismodified = YES; + if(hbp == curbuf){ + screen_label_line(hbp,"(modified)",LABEL_C_MODIFIED); + }/* End IF */ + }/* End IF */ + + checkpoint_modified = YES; + +/* + * We special case position zero because this is an easy case, and is common + * when HK is being used a lot + */ + if(position == 0){ + if((olbp = hbp->first_line) != NULL){ + if(olbp->byte_count == 0){ + olbp = olbp->next_line; + buff_free_line_buffer(hbp->first_line); + }/* End IF */ + }/* End IF */ + hbp->first_line = lbp; + while(lbp->next_line) lbp = lbp->next_line; + lbp->next_line = olbp; + if(olbp) olbp->prev_line = lbp; + hbp->zee += count; + hbp->dot += count; + hbp->pos_cache.lbp = NULL; + return; + }/* End IF */ + +/* + * Also special check for position zee since this is also an easy case + */ + if(position == hbp->zee){ + olbp = hbp->first_line; + if(hbp->pos_cache.lbp) olbp = hbp->pos_cache.lbp; + while(olbp->next_line) olbp = olbp->next_line; + if(olbp->byte_count == 0){ + olbp = olbp->prev_line; + buff_free_line_buffer(olbp->next_line); + }/* End IF */ + olbp->next_line = lbp; + lbp->prev_line = olbp; + if(hbp->dot == hbp->zee) hbp->dot += count; + hbp->zee += count; + hbp->pos_cache.lbp = NULL; + return; + }/* End IF */ + +/* + * Ok, if the insertion is not at the begining or the end of the buffer, then + * we just have to do it the hard way... + */ + olbp = buff_find_line(hbp,position); + offset = buff_find_offset(hbp,olbp,position); + +/* + * Bulk inserts should always be guarenteed aligned. + */ + if(offset){ + error_message("BULK_INSERT: internal error! non-aligned bulk insert"); + return; + }/* End IF */ + + if(olbp->prev_line) olbp->prev_line->next_line = lbp; + lbp->prev_line = olbp->prev_line; + + while(lbp->next_line) lbp = lbp->next_line; + lbp->next_line = olbp; + olbp->prev_line = lbp; + + if(hbp->dot >= position) hbp->dot += count; + if(hbp->pos_cache.base >= position) hbp->pos_cache.base += count; + hbp->zee += count; + + return; + +}/* End Routine */ + + + +/* ALLOCATE_LINE_BUFFER - Routine to allocate a line buffer structure + * + * Function: + * + * This routine is used to allocate a line buffer structure with + * a data buffer large enough to accommodate the specified number + * of bytes. + */ +struct buff_line * +allocate_line_buffer(size) +register int size; +{ +register struct buff_line *lbp; + + PREAMBLE(); +/* + * If he is asking for a size which will result in the minimum allocation, try + * to satisfy the request off of the lookaside list. + */ + if(size <= INITIAL_LINE_BUFFER_SIZE){ + if((lbp = line_buffer_lookaside_list) != NULL){ + line_buffer_lookaside_list = lbp->next_line; + lbp->next_line = NULL; + lbp->prev_line = NULL; + lbp->format_line = NULL; + lbp->byte_count = 0; + lbp->lin_magic = MAGIC_LINE; + return(lbp); + }/* End IF */ + }/* End IF */ + +/* + * Lookaside list is empty, or he is asking for a non-standard size + */ + lbp = (struct buff_line *)tec_alloc(TYPE_C_LINE,sizeof(struct buff_line)); + if(lbp == NULL) return(NULL); + + if(size <= INITIAL_LINE_BUFFER_SIZE){ + lbp->buffer_size = INITIAL_LINE_BUFFER_SIZE; + }/* End IF */ + else if(size < INITIAL_LINE_BUFFER_SIZE + INCREMENTAL_LINE_BUFFER_SIZE){ + lbp->buffer_size = INITIAL_LINE_BUFFER_SIZE + + INCREMENTAL_LINE_BUFFER_SIZE; + } + else { + lbp->buffer_size = size + INCREMENTAL_LINE_BUFFER_SIZE - 1; + lbp->buffer_size -= lbp->buffer_size % INCREMENTAL_LINE_BUFFER_SIZE; + }/* End IF */ + + lbp->buffer = tec_alloc(TYPE_C_LINEBUF,lbp->buffer_size); + if(lbp->buffer == NULL){ + tec_release(TYPE_C_LINE,(char *)lbp); + return(NULL); + }/* End IF */ + + lbp->byte_count = 0; + lbp->next_line = NULL; + lbp->prev_line = NULL; + lbp->format_line = NULL; + lbp->lin_magic = MAGIC_LINE; + return(lbp); + +}/* End Routine */ + + +/* BUFF_FREE_LINE_BUFFER - Free up the associated storage + * + * Function: + * + * This routine is called when a line buffer is deleted. It cleans up any + * associated storage as well as the buffer itself. + */ +void +buff_free_line_buffer(lbp) +register struct buff_line *lbp; +{ + + PREAMBLE(); + + if(lbp->format_line){ + screen_free_format_lines(lbp->format_line); + lbp->format_line = NULL; + }/* End IF */ + +/* + * If this is a standard size line buffer, place it back on the lookaside list + * rather than giving it back to tec_alloc. + */ + lbp->lin_magic = 0; + + if(lbp->buffer_size == INITIAL_LINE_BUFFER_SIZE){ + lbp->lin_magic = MAGIC_LINE_LOOKASIDE; + lbp->next_line = line_buffer_lookaside_list; + line_buffer_lookaside_list = lbp; + return; + }/* End IF */ + + tec_release(TYPE_C_LINEBUF,lbp->buffer); + tec_release(TYPE_C_LINE,(char *)lbp); + +}/* End Routine */ + +/* BUFF_FREE_LINE_BUFFER_LIST - Release a whole list of line buffers + * + * Function: + * + * This routine will delete an entire list of line buffers + */ +void +buff_free_line_buffer_list(lbp) +register struct buff_line *lbp; +{ +register struct buff_line *olbp; + + PREAMBLE(); + + while((olbp = lbp)){ + lbp = lbp->next_line; + buff_free_line_buffer(olbp); + }/* End While */ + +}/* End Routine */ + +/* BUFF_DEALLOCATE_LINE_BUFFER_LOOKASIDE_LIST - Free up lookaside list + * + * Function: + * + * This routine frees up any format_lines we've cached on our local + * lookaside list. + */ +void +buff_deallocate_line_buffer_lookaside_list() +{ +register struct buff_line *lbp; + + PREAMBLE(); + + while((lbp = line_buffer_lookaside_list) != NULL){ + line_buffer_lookaside_list = lbp->next_line; + lbp->lin_magic = 0; + tec_release(TYPE_C_LINEBUF,lbp->buffer); + tec_release(TYPE_C_LINE,(char *)lbp); + }/* End While */ + +}/* End Routine */ + + + +#ifdef DEBUG_DUMPING +buff_dump_buffer(who_string,hbp,channel) +char *who_string; +register struct buff_header *hbp; +FILE *channel; +{ +register struct buff_line *lbp; +register int i,j; + + PREAMBLE(); + + fprintf(channel,"%s requested buffer dump:\n"); + + for(i = 0, lbp = hbp->first_line; lbp != NULL; i++,lbp = lbp->next_line){ + fprintf(channel,"line %4d (count %4d)'",i,lbp->byte_count); + buff_dump_line(lbp,channel); + fprintf(channel,"'\n"); + }/* End FOR */ + + fflush(debug_chan); + +}/* End Routine */ + +buff_dump_line(lbp,channel) +register struct buff_line *lbp; +FILE *channel; +{ +register struct buff_header *hbp = curbuf; +register int i,j; + + PREAMBLE(); + + for(j = 0; j < lbp->byte_count; j++){ + if(lbp->buffer[j] == '\n'){ + fprintf(channel,""); + }/* End IF */ + else fprintf(channel,"%c",lbp->buffer[j]); + }/* End FOR */ + +}/* End Routine */ + +#endif + +unsigned int +stringHash( char *str ) +{ + unsigned long hash = 5381; + int c; + + while ((c = *str++) & 0xFF ) + { + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + + return hash; +} diff --git a/teccmd.c b/teccmd.c new file mode 100644 index 0000000..f55af95 --- /dev/null +++ b/teccmd.c @@ -0,0 +1,2668 @@ +char *teccmd_c_version = "teccmd.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/teccmd.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* teccmd.c + * Subroutines which implement (usually large) TECO commands + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +/* + * Define constants for search table code + */ +#define SEARCH_M_MATCHREPEAT 0x80 +#define SEARCH_M_NOT 0x40 + +#define SEARCH_M_TYPE 0x0F +#define SEARCH_C_BYMASK 1 +#define SEARCH_C_ATLEASTONE 2 +#define SEARCH_C_QREGNUM 3 +#define SEARCH_C_QREGCHARS 4 + +/* + * Globals + */ + int last_search_pos1; + int last_search_pos2; + int last_search_status; + struct tags *current_tags; + +#ifdef CHECKPOINT + static char *checkpoint_filename = NULL; +#endif /* CHECKPOINT */ + +/* + * External Routines, etc. + */ + extern int forced_height,forced_width; + extern char eight_bit_output_flag,hide_cr_flag; + +#ifdef CHECKPOINT + void cmd_checkpoint(void); +#endif /* CHECKPOINT */ + + struct tags *tag_load_file(); + + void srch_setbits(int *bit_table,char *string); + void srch_setbit(int *bit_table,unsigned char value); + void srch_setallbits(int *bit_table); + void srch_invert_sense(int *bit_table); + int cmd_writebak(int,char *,char *,char *,int); + + extern struct buff_header *curbuf; + extern struct buff_header *buffer_headers; + extern char waiting_for_input_flag; + extern char alternate_escape_character; + extern char alternate_delete_character; + extern char intr_flag; + extern int term_lines; + extern char checkpoint_flag; + extern char checkpoint_enabled; + extern int checkpoint_interval; + extern char checkpoint_modified; + extern char resize_flag; + extern unsigned int IntBits[BITS_PER_INT]; + extern struct search_buff search_string; + extern char suspend_is_okay_flag; + +/* CMD_SEARCH - Execute a search command + * + * Function: + * + * This is the runtime execution of the search command. ARG1 and ARG2 may + * specify a buffer range a,bS in which case the search is + * constrained within the range. In this case, the direction of the search + * is determined by whether ARG1 is greater than or less than ARG2. + * If ARG2 == -1, then it is an ignored argument and ARG1 specifies the + * direction of search and the number of times to search. + */ +int +cmd_search(arg1,arg2,search_tbl) +int arg1; +int arg2; +struct search_buff *search_tbl; +{ +int count; +char forwards; +int status; +int pos1; +int pos2; +int original_dot; + + PREAMBLE(); + +/* + * Compile the search Q-register into a search table + */ + if(compile_search_string(search_tbl) == FAIL) return(FAIL); + +/* + * Before looking at the arguments, set up the defaults + */ + original_dot = curbuf->dot; + count = 1; + forwards = 1; + pos1 = 0; + pos2 = curbuf->zee; + +/* + * If ARG2 is -1, then he did not specify a range, but rather a count + */ + if(arg2 == -1){ + if(arg1 < 0){ + forwards = 0; + count = -arg1; + }/* End IF */ + + else count = arg1; + }/* End IF */ +/* + * Else, if ARG2 != -1, then he specified a range for the search + */ + else { + if(arg1 < arg2){ + curbuf->dot = pos1 = arg1; + pos2 = arg2; + }/* End IF */ + + else { + forwards = 0; + pos1 = arg2; + curbuf->dot = pos2 = arg1; + }/* End Else */ + }/* End Else */ + +/* + * Now implement the search + */ + while(count--){ + if(forwards){ + status = cmd_forward_search(pos1,pos2,search_tbl); + }/* End IF */ + + else { + status = cmd_reverse_search(pos1,pos2,search_tbl); + }/* End IF */ + + if(status == FAIL){ + curbuf->dot = original_dot; + last_search_pos1 = last_search_pos2 = -1; + last_search_status = 0; + return(FAIL); + }/* End IF */ + }/* End While */ + + last_search_status = -1; + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_FORWARD_SEARCH - Search in a forward direction + * + * Function: + * + * This routine is used when the search progresses forward + */ +int +cmd_forward_search(pos1,pos2,search_tbl) +int pos1; +int pos2; +struct search_buff *search_tbl; +{ +register struct search_element *ep; +register unsigned char buffer_char; +register int table_position; +int dotpos; +char token_match; +char tmp_message[LINE_BUFFER_SIZE]; +struct position_cache base_position; +struct position_cache running_position; + + PREAMBLE(); + + dotpos = curbuf->dot; +/* + * Insure the search string is non-null + */ + if(search_tbl->length <= 0){ + error_message("?Null Search String"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + + if(dotpos < pos1) return(FAIL); + + table_position = 0; + ep = &search_tbl->data[table_position]; + buffer_char = buff_cached_contents(curbuf,dotpos,&base_position); + running_position = base_position; + + while(dotpos < pos2){ + + if(table_position == 0){ + last_search_pos1 = dotpos; + base_position = running_position; + }/* End IF */ + + buffer_char = BUFF_CACHE_CONTENTS(&running_position); + BUFF_INCREMENT_CACHE_POSITION(&running_position); + dotpos++; + token_match = 1; + + switch(ep->type & SEARCH_M_TYPE){ + case SEARCH_C_BYMASK: + if((ep->bitmask.intarray[buffer_char / BITS_PER_INT] & + IntBits[buffer_char % BITS_PER_INT]) == 0){ + token_match = 0; + break; + }/* End IF */ + if(ep->type & SEARCH_M_MATCHREPEAT){ + while(dotpos < pos2){ + buffer_char = BUFF_CACHE_CONTENTS(&running_position); + if((ep->bitmask.intarray[buffer_char / BITS_PER_INT] & + IntBits[buffer_char % BITS_PER_INT]) == 0) break; + BUFF_INCREMENT_CACHE_POSITION(&running_position); + dotpos++; + }/* End While */ + }/* End IF */ + break; + case SEARCH_C_QREGCHARS: + { struct buff_header *qbp; + register int i; + qbp = buff_qfind(ep->value,0); + token_match = 0; + if(qbp == NULL) break; + for(i = 0; i < qbp->zee; i++){ + if(buffer_char == buff_contents(qbp,i)) token_match = 1; + }/* End FOR */ + if(ep->type & SEARCH_M_NOT){ + token_match = token_match ? 0 : 1; + } + }/* End Block */ + break; + case SEARCH_C_ATLEASTONE: + error_message("?^EM not implemented..."); + search_tbl->error_message_given = YES; + return(FAIL); + case SEARCH_C_QREGNUM: + { struct buff_header *qbp; + qbp = buff_qfind(ep->value,0); + token_match = 0; + if(qbp == NULL) break; + if(buffer_char == qbp->ivalue) token_match = 1; + if(ep->type & SEARCH_M_NOT){ + token_match = token_match ? 0 : 1; + }/* End IF */ + }/* End Block */ + break; + default: + sprintf(tmp_message, + "SEARCH: Illegal token type %d - internal error", + ep->type); + tec_panic(tmp_message); + return(FAIL); + }/* End Switch */ + + if(token_match){ + table_position += 1; + ep++; + if(table_position == search_tbl->length){ + curbuf->dot = last_search_pos2 = dotpos; + {/* Local Block */ + register struct buff_header *hbp; + hbp = buff_qfind('_',1); + if(hbp == NULL) return(FAIL); + hbp->ivalue = last_search_pos1; + }/* End Block */ + return(SUCCESS); + }/* End IF */ + }/* End IF */ + + else { + if(intr_flag){ + error_message("?Search aborted"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + table_position = 0; + ep = &search_tbl->data[table_position]; + dotpos = last_search_pos1 + 1; + running_position = base_position; + BUFF_INCREMENT_CACHE_POSITION(&running_position); + }/* End IF */ + + }/* End While */ + + return(FAIL); + +}/* End Routine */ + + + +/* CMD_REVERSE_SEARCH - Search in a reverse direction + * + * Function: + * + * This routine is used when the search progresses backwards + */ +int +cmd_reverse_search(pos1,pos2,search_tbl) +int pos1; +int pos2; +struct search_buff *search_tbl; +{ +register struct search_element *ep; +register unsigned char buffer_char; +register int table_position; +int dotpos; +char token_match; +char tmp_message[LINE_BUFFER_SIZE]; +struct position_cache base_position; +struct position_cache running_position; + + PREAMBLE(); + + dotpos = curbuf->dot; +/* + * Insure the search string is non-null + */ + if(search_tbl->length <= 0){ + error_message("?Null Search String"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + + if(dotpos > pos2) return(FAIL); + + table_position = search_tbl->length - 1; + ep = &search_tbl->data[table_position]; + buffer_char = buff_cached_contents(curbuf,dotpos,&base_position); + running_position = base_position; + + while(dotpos > pos1){ + + if(table_position == (search_tbl->length - 1)){ + last_search_pos2 = dotpos; + base_position = running_position; + }/* End IF */ + + dotpos--; + BUFF_DECREMENT_CACHE_POSITION(&running_position); + buffer_char = BUFF_CACHE_CONTENTS(&running_position); + token_match = 1; + + switch(ep->type & SEARCH_M_TYPE){ + case SEARCH_C_BYMASK: + if((ep->bitmask.intarray[buffer_char / BITS_PER_INT] & + IntBits[buffer_char % BITS_PER_INT]) == 0){ + token_match = 0; + break; + }/* End IF */ + if(ep->type & SEARCH_M_MATCHREPEAT){ + while(dotpos > pos1){ + BUFF_DECREMENT_CACHE_POSITION(&running_position); + dotpos--; + buffer_char = BUFF_CACHE_CONTENTS(&running_position); + if(ep->bitmask.intarray[buffer_char / BITS_PER_INT] & + IntBits[buffer_char % BITS_PER_INT]) continue; + dotpos++; + BUFF_INCREMENT_CACHE_POSITION(&running_position); + break; + }/* End While */ + }/* End IF */ + break; + case SEARCH_C_QREGCHARS: + { struct buff_header *qbp; + register int i; + qbp = buff_qfind(ep->value,0); + token_match = 0; + if(qbp == NULL) break; + for(i = 0; i < qbp->zee; i++){ + if(buffer_char == buff_contents(qbp,i)) token_match = 1; + }/* End FOR */ + }/* End Block */ + break; + case SEARCH_C_ATLEASTONE: + error_message("?^EM not implemented..."); + search_tbl->error_message_given = YES; + return(FAIL); + case SEARCH_C_QREGNUM: + { struct buff_header *qbp; + qbp = buff_qfind(ep->value,0); + token_match = 0; + if(qbp == NULL) break; + if(buffer_char == qbp->ivalue) token_match = 1; + }/* End Block */ + break; + default: + sprintf(tmp_message, + "SEARCH: Illegal token type %d - internal error", + ep->type); + tec_panic(tmp_message); + return(FAIL); + }/* End Switch */ + + if(token_match){ + table_position -= 1; + ep--; + if(table_position < 0){ + curbuf->dot = last_search_pos1 = dotpos; + {/* Local Block */ + register struct buff_header *hbp; + hbp = buff_qfind('_',1); + if(hbp == NULL) return(FAIL); + hbp->ivalue = last_search_pos2; + }/* End Block */ + return(SUCCESS); + }/* End IF */ + }/* End IF */ + + else { + if(intr_flag){ + error_message("?Search aborted\n"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + table_position = search_tbl->length - 1; + ep = &search_tbl->data[table_position]; + dotpos = last_search_pos2 - 1; + running_position = base_position; + BUFF_DECREMENT_CACHE_POSITION(&running_position); + }/* End IF */ + + }/* End While */ + + return(FAIL); + +}/* End Routine */ + + + +/* SET_SEARCH_STRING_WITH_UNDO - Sets Q-register '_' to the new search string + * + * Function: + * + * This routine is called from the exec functions when a search string + * is to be set. It takes care of loading Q-register '_' with the new + * search string, as well as setting up all the undo tokens to insure + * that this is all reversable. + */ +int +set_search_string_with_undo(string,uct) +char *string; +struct cmd_token *uct; +{ +register struct buff_header *qbp; +register struct undo_token *ut; +register int i,c; +register char *cp; +int new_length; + + PREAMBLE(); + +/* + * Get a pointer to the special Q-register which holds the search string. If + * it does not exist, have it be created automatically. + */ + qbp = buff_qfind('_',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + +/* + * This code allows us to load the numeric half of the search string + * Q-register with the length of the string we just found. In order + * to be able to undo it, we have to protect the previous value, and + * it just happens that by doing it here we probably catch all the + * places where the search could have taken place. Then again, maybe + * not (search really should not be calling this routine repeatedly + * in an iteration or macro, for instance). If this gets cleaned up, + * then this code might not be appropriate... XXX + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_UQREGISTER; + ut->iarg1 = '_'; + ut->iarg2 = qbp->ivalue; + + if(string == NULL) return(SUCCESS); + if((new_length = strlen(string)) == 0) return(SUCCESS); + +/* + * Set up undo tokens for the search_state and search_position information + * which typically gets used to pass search information between halves of + * a search / replace type command. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SET_SEARCH_GLOBALS; + ut->iarg1 = last_search_pos1; + ut->iarg2 = last_search_pos2; + ut->carg1 = (char *)last_search_status; + +/* + * If the new length is the same as the old length, there is a chance we are + * just setting the same search string over again. To find out, we actually + * have to loop through comparing bytes in the Q-register to bytes in the + * supplied string. If none of them are different, we can just return without + * changing the search string. This happens often in iterations... + */ + if(new_length == qbp->zee){ + cp = string; + for(i = 0; i < new_length; i++){ + c = buff_contents(qbp,i); + if(*cp++ != c) break; + }/* End FOR */ + + if(i == new_length) return(SUCCESS); + + }/* End IF */ +/* + * At this point we set an undo token so that if the following stuff gets + * undone, we will re-compile the search string. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SET_SEARCH_STRING; +/* + * Delete the current contents of the search string Q-register to make room + * for the new contents. + */ + if(qbp->zee > 0) buff_delete_with_undo(uct,qbp,0,qbp->zee); +/* + * Set up the undo token to delete the text we are going to insert into the + * search string Q-register. + */ + buff_insert_with_undo(uct,qbp,0,string,new_length); + + return(SUCCESS); + +}/* End Routine */ + + + +/* COMPILE_SEARCH_STRING - Creates the search table for a given input string + * + * Function: + * + * This routine parses the input search string and creates the search + * table which will describe to the search routines how to match the + * buffer characters. Note that the search table must be constructed + * in such a way that it works equally well backward or forward. + */ +int +compile_search_string(search_tbl) +struct search_buff *search_tbl; +{ +register int position; +register char c; +register char *cp; +register struct buff_header *qbp; +register struct search_element *ep; +int radix; +int bracket_bits[256/BITS_PER_INT]; +char tmp_message[LINE_BUFFER_SIZE]; +char either_case_flag = YES; +char bracket_wildcard = NO; +char bracket_not_flag = NO; +char not_flag = NO; +char matchrepeat_flag = NO; + + PREAMBLE(); + +#define QBUF_CHAR() \ + (position < qbp->zee ? *cp++ = buff_contents(qbp,position++) : 0) + + search_tbl->error_message_given = NO; + +/* + * Get a pointer to the special Q-register which holds the search string. If + * it does not exist, have it be created automatically. + */ + qbp = buff_qfind('_',1); + if(qbp == NULL){ + error_message("Internal error - qfind returned null"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + + if(qbp->zee >= SEARCH_STRING_MAX){ + error_message("Search Q-register too long"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + + search_string.length = 0; + position = 0; + cp = search_string.input; + ep = search_string.data; + bzero(&ep->bitmask,sizeof(ep->bitmask)); + + while((c = QBUF_CHAR())){ +/* + * Check for CNTRL_E which is the standard lead-in for wildcard search + * strings. + */ + if(c == CNTRL_E){ + c = QBUF_CHAR(); + switch(c){ +/* + * [ specifies a list of possible matches, ANY of which satisfy the + * position. + */ + case '[': + bzero(bracket_bits,sizeof(bracket_bits)); + bracket_wildcard = YES; + if(not_flag){ + not_flag = NO; + bracket_not_flag = YES; + }/* End IF */ + continue; +/* + * A specifies any ALPHABETIC character as a match. Thus any upper or lower + * case character in the range a-z will match. + */ + case 'A': case 'a': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "abcdefghijklmnopqrstuvwxyz"); + srch_setbits((int *)&ep->bitmask, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + break; +/* + * B specifies any separator character, when separator is defined as any + * non-alphanumeric character. + */ + case 'B': case 'b': + { + register int i; + ep->type = SEARCH_C_BYMASK; + for(i = 0; i < 256; i++){ + if(!isalnum(i)) srch_setbit((int *)&ep->bitmask,i); + }/* End FOR */ + break; + } +/* + * C specifies any symbol constituent. This is any alpha-numeric character, + * or dot (.), dollar sign ($), or underscore (_). + */ + case 'C': case 'c': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "abcdefghijklmnopqrstuvwxyz"); + srch_setbits((int *)&ep->bitmask, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + srch_setbits((int *)&ep->bitmask,"0123456789.$_"); + break; +/* + * D specifies that any digit (0 to 9) is acceptable. + */ + case 'D': case 'd': + ep->type = SEARCH_C_BYMASK | SEARCH_M_MATCHREPEAT; + srch_setbits((int *)&ep->bitmask,"0123456789"); + break; +/* + * E or ^E is a Video TECO only definition which means that whether searches + * are case sensitive or not should be toggled. + */ + case 'E': case 'e': case CNTRL_E: + either_case_flag ^= 1; + continue; +/* + * G followed by a Q register name matches any character which is in the + * text portion of the Q register. + */ + case 'G': case 'g': + if(bracket_wildcard){ + error_message("^EG inside ^E[,,,] illegal"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + ep->type = SEARCH_C_QREGCHARS; + ep->value = QBUF_CHAR(); + break; +/* + * L matches any line terminator character. + */ + case 'L': case 'l': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask,"\n\r\f"); + break; +/* + * M is a flag that says any number of the following character (but at least + * one) are acceptable. Thus ^EMA would match A or AA or AAA, etc. + */ + case 'M': case 'm': + matchrepeat_flag = YES; + continue; +/* + * R matches any alphanumeric + */ + case 'R': case 'r': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "abcdefghijklmnopqrstuvwxyz"); + srch_setbits((int *)&ep->bitmask, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + srch_setbits((int *)&ep->bitmask,"0123456789"); + break; +/* + * S matches any number of tabs and spaces, in any mixture (but there must + * be at least one). + */ + case 'S': case 's': + ep->type = SEARCH_C_BYMASK | SEARCH_M_MATCHREPEAT; + srch_setbits((int *)&ep->bitmask," \t"); + break; +/* + * U matches against the ASCII code which is contained in the Q register's + * numeric storage. + */ + case 'U': case 'u': + if(bracket_wildcard){ + error_message("^EU inside ^E[,,,] illegal"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End IF */ + ep->type = SEARCH_C_QREGNUM; + ep->value = QBUF_CHAR(); + break; +/* + * V matches any lowercase letter. + */ + case 'V': case 'v': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "abcdefghijklmnopqrstuvwxyz"); + break; +/* + * W matches any uppercase letter + */ + case 'W': case 'w': + ep->type = SEARCH_C_BYMASK; + srch_setbits((int *)&ep->bitmask, + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + break; +/* + * X is similar to ^X in that it matches any character + */ + case 'X': case 'x': + ep->type = SEARCH_C_BYMASK; + srch_setallbits((int *)&ep->bitmask); + break; +/* + * The < character opens the sequence for ^E where nnn is a numeric + * (ASCII) code to search for. Classic TECO says that this code is octal, + * but Video TECO defaults to decimal, octal if the first digit is zero, + * or hex if the first two digits are 0x. + */ + case '<': + ep->value = 0; + radix = 10; + c = QBUF_CHAR(); + if(c == '0'){ + radix = 8; + c = QBUF_CHAR(); + if(c == 'x' || c == 'X'){ + radix = 16; + c = QBUF_CHAR(); + }/* End IF */ + }/* End IF */ + while(c && c != '>'){ + if(c >= '0' && c <= '7'){ + ep->value *= radix; + ep->value += c - '0'; + c = QBUF_CHAR(); + continue; + }/* End IF */ + if(c >= '8' && c <= '9' && radix > 8){ + ep->value *= radix; + ep->value += c - '0'; + c = QBUF_CHAR(); + continue; + }/* End IF */ + if(c >= 'a' && c <= 'f' && radix == 16){ + c = TOUPPER(c); + }/* End IF */ + if(c >= 'A' && c <= 'F' && radix == 16){ + ep->value *= radix; + ep->value += c - 'A' + 10; + c = QBUF_CHAR(); + continue; + }/* End IF */ + sprintf(tmp_message, + "?Illegal ^E code '%c' in search string",c); + search_tbl->error_message_given = YES; + error_message(tmp_message); + return(FAIL); + }/* End While */ + if(c == '>') c = QBUF_CHAR(); + else { + error_message("?Missing '>' in ^E search string"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End Else */ + ep->type = SEARCH_C_BYMASK; + srch_setbit((int *)&ep->bitmask,ep->value); + break; +/* + * This case is really just to catch the user typing ^E followed by the + * end of the search string. We just make it search for ^E in this case. + */ + case '\0': + ep->type = SEARCH_C_BYMASK; + srch_setbit((int *)&ep->bitmask,CNTRL_E); + break; +/* + * Any unrecognized ^E code just searches for the exact character which + * follows the ^E. This is probably not very good, we should issue an + * error message instead. + */ + default: + ep->type = SEARCH_C_BYMASK; + srch_setbit((int *)&ep->bitmask,c); + break; + }/* End Switch */ + }/* End IF */ +/* + * The ^X code embedded in a search string means match any character at + * this position. + */ + else if(c == CNTRL_X){ + ep->type = SEARCH_C_BYMASK; + srch_setallbits((int *)&ep->bitmask); + }/* End Else */ +/* + * The ^N code embedded in a search string means match any character EXCEPT + * the one that follows. + */ + else if(c == CNTRL_N){ + not_flag = YES; + continue; + }/* End Else */ +/* + * Else, here if it is just a normal character which should be searched for + */ + else { + ep->type = SEARCH_C_BYMASK; + srch_setbit((int *)&ep->bitmask,c); + if(either_case_flag){ + if(islower((int)c)) srch_setbit((int *)&ep->bitmask,TOUPPER((int)c)); + if(isupper((int)c)) srch_setbit((int *)&ep->bitmask,TOLOWER((int)c)); + }/* End IF */ + }/* End Else */ + +/* + * If sense has been inverted with ^N, invert the bits + */ + if(not_flag){ + if(ep->type & SEARCH_C_BYMASK){ + srch_invert_sense((int *)&ep->bitmask); + }/* End IF */ + ep->type &= ~SEARCH_M_MATCHREPEAT; + ep->type |= SEARCH_M_NOT; + not_flag = NO; + }/* End IF */ +/* + * If already inside a ^E[a,b,c] type wildcard, check for comma or close + * bracket. + */ + if(bracket_wildcard){ + c = QBUF_CHAR(); + switch(c){ + case ',': + { + register int i; + for(i = 0; i < 256 / BITS_PER_INT; i++){ + bracket_bits[i] |= ep->bitmask.intarray[i]; + }/* End FOR */ + bzero(&ep->bitmask,sizeof(ep->bitmask)); + continue; + } + case ']': + { + register int i; + for(i = 0; i < 256 / BITS_PER_INT; i++){ + ep->bitmask.intarray[i] |= bracket_bits[i]; + }/* End FOR */ + if(bracket_not_flag){ + srch_invert_sense((int *)&ep->bitmask); + ep->type &= ~SEARCH_M_MATCHREPEAT; + bracket_not_flag = NO; + }/* End IF */ + bracket_wildcard = NO; + break; + } + default: + error_message("Illegal syntax in [,,,] construct"); + search_tbl->error_message_given = YES; + return(FAIL); + }/* End Switch */ + }/* End IF */ +/* + * Test for match repeating characters flag + */ + if(matchrepeat_flag){ + ep->type |= SEARCH_M_MATCHREPEAT; + matchrepeat_flag = NO; + }/* End IF */ +/* + * Point to the next entry in the search string table + */ + ep++; + bzero(&ep->bitmask,sizeof(ep->bitmask)); + search_string.length += 1; + + }/* End While */ + + *cp = '\0'; + + return(SUCCESS); + +}/* End Routine */ + + + +/* SRCH_SETALLBITS - Set ALL the bits on for a search table entry + * + * Function: + * + * This routine makes ANY character match the search string table + * entry. + */ +void +srch_setallbits(bit_table) +register int *bit_table; +{ +register int i; + + PREAMBLE(); + + for(i = 0; i < 256 / BITS_PER_INT; i++){ + bit_table[i] = ~0; + }/* End FOR */ + +}/* End Routine */ + +/* SRCH_INVERT_SENSE - Invert the sense of a search table entry + * + * Function: + * + * This routine is called when a 'NOT' modifier has been used, + * meaning that just the opposite matching should occur. We + * simply invert the setting of the bits in the search table entry. + */ +void +srch_invert_sense(bit_table) +register int *bit_table; +{ +register int i; + + PREAMBLE(); + + for(i = 0; i < 256 / BITS_PER_INT; i++){ + bit_table[i] ^= ~0; + }/* End FOR */ + +}/* End Routine */ + +/* SRCH_SETBITS - Set the corresponding search bits to 'on' + * + * Function: + * + * This routine is called with a zero terminated string of characters. + * It sets each corresponding bit in the search table to a 1. + */ +void +srch_setbits(bit_table,string) +register int *bit_table; +register char *string; +{ +register int c; + + PREAMBLE(); + + while((c = *string++)){ + bit_table[c/BITS_PER_INT] |= IntBits[c%BITS_PER_INT]; + }/* End While */ + +}/* End Routine */ + +/* SRCH_SETBIT - Set the corresponding search bit to 'on' + * + * Function: + * + * This routine is called with a char whose corresponding bit should + * be set in the search table. + */ +void +srch_setbit(bit_table,value) +register int *bit_table; +register unsigned char value; +{ + + PREAMBLE(); + + bit_table[value/BITS_PER_INT] |= IntBits[value%BITS_PER_INT]; + +}/* End Routine */ + + + +/* CMD_WRITE - Implements the EW command by writing out a buffer + * + * Function: + * + * This routine will write out the contents of the buffer after creating + * the appropriate backup files. If there is not already a file with the + * a .OLD extension, this is created. Otherwise, a .BAK file is created. + * If the name is so long that we can't append a of suffix, we have to + * hack it up a bit. This is not guaranteed to work, but... + */ +int +cmd_write(hbp,filename) +struct buff_header *hbp; +char *filename; +{ +char base_filename[TECO_FILENAME_COMPONENT_LENGTH + 1]; +char path_name[TECO_FILENAME_TOTAL_LENGTH + 1]; +char tmp_filename[TECO_FILENAME_TOTAL_LENGTH + 1]; +char tmp_message[LINE_BUFFER_SIZE]; +register char *cp1,*cp2; +register int i; +int path_len; +int file_len; +int combined_len; +int fi; +int status; + + PREAMBLE(); + +#ifdef UNIX +/* + * First step is to separate the path elements from the filename itself. + * To do this, we search for the directory path separator. + */ + cp1 = cp2 = filename; + while(*cp1){ + if(*cp1++ == '/') cp2 = cp1; + }/* End While */ + + combined_len = strlen(filename); + file_len = strlen(cp2); + path_len = combined_len - file_len; + +/* + * The filename really should not be longer than this. + */ + if(file_len > TECO_FILENAME_COMPONENT_LENGTH){ + sprintf(tmp_message,"?Filename is too long <%s>",cp2); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Make a copy of the filename portion for safe keeping. + */ + (void) strcpy(base_filename,cp2); +/* + * And do the same for the path portion. + */ + cp1 = path_name; + cp2 = filename; + for(i = 0; i < path_len; i++){ + *cp1++ = *cp2++; + }/* End FOR */ + *cp1 = '\0'; + +/* + * Now the first thing to do is see if the file exists, because this + * will impact us on whether we want to attempt .BAK files + */ + if(hbp->isbackedup == NO){ + + fi = open(filename,O_RDONLY); + if(fi >= 0){ + +#ifdef CREATE_OLD_FILES +#ifdef HAVE_LONG_FILE_NAMES + (void) strcpy(tmp_filename,base_filename); + (void) strcat(tmp_filename,".OLD"); + status = cmd_writebak(fi,filename,path_name,tmp_filename,O_EXCL); +#else + (void) strcpy(tmp_filename,path_name); + (void) strcat(tmp_filename,".TECOLD"); + status = cmd_writebak(fi,filename,tmp_filename,base_filename,O_EXCL); +#endif + if(status == SUCCESS) hbp->isbackedup = YES; + + if(status == FAIL && errno != EEXIST){ + sprintf(tmp_message,"?Error opening <%s>: %s", + filename,error_text(errno)); + error_message(tmp_message); + close(fi); + return(FAIL); + }/* End IF */ +#endif + + if(hbp->isbackedup == NO){ +#ifdef HAVE_LONG_FILE_NAMES + (void) strcpy(tmp_filename,base_filename); + (void) strcat(tmp_filename,".BAK"); + status = cmd_writebak(fi,filename,path_name,tmp_filename,0); +#else + (void) strcpy(tmp_filename,path_name); + (void) strcat(tmp_filename,".TECBAK"); + status = cmd_writebak(fi,filename,tmp_filename,base_filename,0); +#endif + if(status == SUCCESS) hbp->isbackedup = YES; + if(status == FAIL){ + sprintf(tmp_message,"?Error opening <%s>: %s", + filename,error_text(errno)); + error_message(tmp_message); + close(fi); + return(FAIL); + }/* End IF */ + }/* End IF */ + }/* End IF */ + + close(fi); + + }/* End IF */ + +/* END OF UNIX CONDITIONAL CODE */ + +#endif + +#ifdef VMS + fi = creat(filename,0,"mrs = 0"); +#else + fi = open(filename,O_WRONLY|O_CREAT|O_TRUNC,0666); +#endif + + if(fi < 0){ + sprintf(tmp_message,"?Error opening <%s>: %s", + filename,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + status = buff_write(hbp,fi,0,hbp->zee); + + close(fi); + + if(status == FAIL){ + sprintf(tmp_message,"?Error writing <%s>: %s", + filename,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + hbp->ismodified = NO; + if(hbp == curbuf){ + screen_label_line(hbp,"",LABEL_C_MODIFIED); + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_WRITEBAK - Routine to open the BAK or OLD file + * + * Function: + * + * This routine opens a channel to the backup file and creates any + * subdirectories that may be necessary. + */ +int +cmd_writebak(fi,input_filename,pathname,output_filename,open_flags) +int fi; +char *pathname; +char *input_filename; +char *output_filename; +int open_flags; +{ +char tmp_filename[TECO_FILENAME_TOTAL_LENGTH + 1]; +char tmp_message[LINE_BUFFER_SIZE]; +char iobuf[IO_BUFFER_SIZE]; +int fo; +register int i; +register int status; + + PREAMBLE(); + +#ifndef HAVE_LONG_FILE_NAMES + if(mkdir(pathname,0777)){ + if(errno != EEXIST){ + sprintf(tmp_message,"?Error creating subdirectory <%s>: %s", + pathname,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + }/* End IF */ +#endif + + (void) strcpy(tmp_filename,pathname); + if(strlen(tmp_filename)){ + (void) strcat(tmp_filename,"/"); + }/* End IF */ + + (void) strcat(tmp_filename,output_filename); + chmod(tmp_filename,0666); + fo = open(tmp_filename,O_WRONLY|O_CREAT|O_TRUNC|open_flags,0666); + if(fo < 0){ + if(errno == EEXIST){ + chmod(tmp_filename,0444); + return(FAIL); + }/* End IF */ + sprintf(tmp_message,"?Error opening <%s>: %s", + tmp_filename,error_text(errno)); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + while(1){ + i = read(fi,iobuf,IO_BUFFER_SIZE); + if(i < 0){ + sprintf(tmp_message,"?Error reading <%s>: %s", + input_filename,error_text(errno)); + error_message(tmp_message); + close(fo); + return(FAIL); + }/* End IF */ + + status = write(fo,iobuf,(unsigned)i); + if(status < 0){ + sprintf(tmp_message,"?Error writing <%s>: %s", + tmp_filename,error_text(errno)); + error_message(tmp_message); + close(fo); + return(FAIL); + }/* End IF */ + + if(i < IO_BUFFER_SIZE) break; + + }/* End While */ + + close(fo); + chmod(tmp_filename,0444); + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_WORDMOVE - Here to move within the edit buffer by words + * + * Function: + * + * Here in response to one of the 'word' commands. We move the + * current edit position forward or backward by the specified + * number of words. + */ +int +cmd_wordmove(count) +register int count; +{ + register int c; + + PREAMBLE(); + + while(count > 0){ + while(1){ + if(curbuf->dot == curbuf->zee) break; + c = buff_contents(curbuf,curbuf->dot); + if(isspace(c)) break; + curbuf->dot++; + }/* End While */ + + while(1){ + if(curbuf->dot == curbuf->zee) break; + c = buff_contents(curbuf,curbuf->dot); + if(!isspace(c)) break; + curbuf->dot++; + }/* End While */ + + count -= 1; + + }/* End While */ + + while(count < 0){ + while(1){ + if(curbuf->dot == 0) break; + c = buff_contents(curbuf,curbuf->dot-1); + if(isspace(c)) break; + curbuf->dot--; + }/* End While */ + + while(1){ + if(curbuf->dot == 0) break; + c = buff_contents(curbuf,curbuf->dot-1); + if(!isspace(c)) break; + curbuf->dot--; + }/* End While */ + + count += 1; + + }/* End While */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_SUSPEND - We get here on a suspend signal from the tty + * + * Function: + * + * Here when the user has typed ^Z. We want to suspend back to the + * original shell. + */ +void +cmd_suspend() +{ +#if defined(UNIX) || defined(VMS) +extern char susp_flag; +#endif + + PREAMBLE(); + +#ifdef UNIX + if(waiting_for_input_flag == YES){ + pause_while_in_input_wait(); + return; + }/* End IF */ + +#ifdef JOB_CONTROL + if(susp_flag++ >= 4){ + signal(SIGTSTP,(void (*)())SIG_DFL); + }/* End IF */ +#endif + +#endif + +#ifdef VMS + if(suspend_is_okay_flag == YES){ + susp_flag++; + }/* End IF */ +#endif + +}/* End Routine */ + +/* CMD_PAUSE - Here when we are ready to suspend back to the shell + * + * Function: + * + * Here when the parser has noticed that the suspend flag is set, and + * is ready for us to suspend the process. + */ +void +cmd_pause() +{ +extern char susp_flag; + +#ifdef VMS +long owner_pid; +long status; + +struct itmlst { + short buffer_length; + short item_code; + char *buffer_address; + long *length_address; + long terminating_zero; +}list; +#endif + + PREAMBLE(); + + restore_tty(); + +#if defined(JOB_CONTROL) || defined(VMS) + +#ifdef CHECKPOINT + alarm(0); +#endif /* CHECKPOINT */ + +#ifdef UNIX + if(suspend_is_okay_flag == YES){ + kill(getpid(),SIGSTOP); + signal(SIGTSTP,(void (*)())cmd_suspend); + } +#endif /* UNIX */ + +#ifdef VMS + owner_pid = 0; + list.buffer_length = sizeof(owner_pid); + list.item_code = JPI$_OWNER; + list.buffer_address = (char *)&owner_pid; + list.length_address = 0; + list.terminating_zero = 0; + sys$getjpiw(0,0,0,&list,0,0,0); + + if(owner_pid){ + status = lib$attach(&owner_pid); + if(status != SS$_NORMAL){ + error_message("?LIB$ATTACH failed"); + susp_flag = 0; + return; + }/* End IF */ + }/* End IF */ + + else { + error_message("?Cannot suspend. No parent process for LIB$ATTACH"); + susp_flag = 0; + return; + }/* End IF */ +#endif + +#ifdef CHECKPOINT + alarm(checkpoint_interval); +#endif /* CHECKPOINT */ + +#endif /* JOB_CONTROL */ + + initialize_tty(); + screen_redraw(); + susp_flag = 0; + +}/* End Routine */ + + + +/* CMD_WINCH - We get here when the OS says the window changed size + * + * Function: + * + * Here when the OS says our window changed size. We just wanna + * call the screen package's resize entry point so it can build + * a new screen for us. + */ +void +cmd_winch() +{ + PREAMBLE(); + +#ifdef ATT_UNIX + signal(SIGWINCH,(void (*)())cmd_winch); +#endif + +#ifdef SEQUOIA + screen_message("WINCH!\g\g"); + screen_refresh(); + return; +#endif /* SEQUOIA */ + + resize_flag = YES; + + if(waiting_for_input_flag){ + screen_resize(); + screen_refresh(); + }/* End IF */ + +}/* End Routine */ + + + +/* CMD_INTERRUPT - We get here on an interrupt signal from the user + * + * Function: + * + * Here when the user has typed ^C. We want to cause any current commands + * to abort. This gives the user a way to break out of infinite iterations + * and macros. + */ +void +cmd_interrupt() +{ + PREAMBLE(); + +#ifdef ATT_UNIX + signal(SIGINT,(void (*)())cmd_interrupt); +#endif + + if(intr_flag++ >= 4){ + if(waiting_for_input_flag == NO){ + restore_tty(); + fprintf(stderr,"TECO aborted...\n"); + signal(SIGINT,(void (*)())SIG_DFL); + kill(getpid(),SIGINT); + }/* End IF */ + }/* End IF */ + + screen_message("interrupt!"); + +}/* End Routine */ + + + +#ifdef CHECKPOINT + +/* CMD_ALARM - Here to when the interval timer expires + * + * Function: + * + * This function is called periodically when the interval timer says + * it is time for us to checkpoint all our buffers. + */ +cmd_alarm() +{ + + PREAMBLE(); + +#if defined(ATT_UNIX) || defined(VMS) + signal(SIGALRM,(void (*)())cmd_alarm); +#endif + + if(waiting_for_input_flag == 0){ + checkpoint_flag = YES; + }/* End IF */ + + else { + if(checkpoint_enabled == YES){ + cmd_checkpoint(); + } + checkpoint_flag = NO; + }/* End Else */ + +#ifdef VMS + alarm(0); +#endif /* VMS */ + alarm(checkpoint_interval); + +}/* End Routine */ + +#endif /* CHECKPOINT */ + + + +#ifdef CHECKPOINT + +/* CMD_CHECKPOINT - Here to write all our buffers to a checkpoint file + * + * Function: + * + * Here we check out all the buffers and write out any which have + * ever been modified. + */ +void +cmd_checkpoint() +{ +register struct buff_header *bp; +int fd = 0; +char temp_buffer[TECO_FILENAME_TOTAL_LENGTH+20]; +char message_save_buff[SCREEN_NOMINAL_LINE_WIDTH]; +char filename_buffer[64]; +register char *cp; +int i,status; + + PREAMBLE(); + + if(checkpoint_modified == NO) return; + checkpoint_modified = NO; + + if(checkpoint_filename == NULL){ + temp_buffer[0] = '\0'; + if(cp = (char *)getenv("HOME")){ + (void) strcpy(temp_buffer,cp); +#ifndef VMS + (void) strcat(temp_buffer,"/"); +#endif /* VMS */ + }/* End IF */ + +#ifndef VMS + strcpy(filename_buffer,".tecXXXXXX"); +#else + strcpy(filename_buffer,"tecXXXXXX.CKP"); +#endif /* VMS */ + cp = mktemp(filename_buffer); + + strcat(temp_buffer,cp); + checkpoint_filename = tec_alloc(TYPE_C_CPERM,strlen(temp_buffer)+1); + if(checkpoint_filename == NULL) return; + strcpy(checkpoint_filename,temp_buffer); + }/* End IF */ + + status = 0; + for(bp = buffer_headers; bp != NULL; bp = bp->next_header){ + if(bp->ismodified){ + status = 1; + }/* End IF */ + }/* End FOR */ + + if(status == 0) return; + + screen_save_current_message(message_save_buff,sizeof(message_save_buff)); + screen_message("checkpointing..."); + screen_refresh(); + + fd = open(checkpoint_filename,O_WRONLY|O_CREAT|O_TRUNC,0666); + if(fd == 0 || fd == -1){ + sprintf(temp_buffer,"?Checkpoint file %s open failed %s", + checkpoint_filename,error_text(errno)); + error_message(temp_buffer); + screen_refresh(); + sleep(2); + return; + }/* End IF */ + + + for(bp = buffer_headers; bp != NULL; bp = bp->next_header){ + if(bp->ismodified == NO && bp->buffer_number <= 0) continue; + if(bp->buffer_number == 0)continue; + i = (78 - strlen(bp->name) - 4) / 2; + cp = temp_buffer; + *cp++ = '\n'; + while(i-- > 0)*cp++ = '*'; + *cp++ = '('; + strcpy(cp,bp->name); + while(*cp)cp++; + *cp++ = ')'; + if(bp->ismodified) *cp++ = '+'; + while(cp < &temp_buffer[78]) *cp++ = '*'; + *cp++ = '\n'; + *cp++ = '\0'; + i = strlen(temp_buffer); + + if(write(fd,temp_buffer,i) != i){ + sprintf(temp_buffer,"?checkpoint failed %s",error_text(errno)); + error_message(temp_buffer); + screen_refresh(); + sleep(2); + close(fd); + return; + }/* End IF */ + + + if(bp->ismodified){ + status = buff_write(bp,fd,0,bp->zee); + }/* End IF */ + + }/* End FOR */ + + close(fd); + + screen_message("checkpointing... done"); + screen_refresh(); + screen_message(message_save_buff); + +}/* End Routine */ + +#endif /* CHECKPOINT */ + + + +#ifdef CHECKPOINT + +/* REMOVE_CHECKPOINT_FILE - On exit we clean up the checkpoint file + * + * Function: + * + * This routine is called when a clean exit is assured. We remove + * the checkpoint file so they don't clutter up the disk. + */ +void +remove_checkpoint_file() +{ + + PREAMBLE(); + + if(checkpoint_filename == NULL) return; +#ifndef VMS + unlink(checkpoint_filename); +#endif + +}/* End Routine */ + +#endif /* CHECKPOINT */ + + + +#if defined(VMS) || defined(STARDENT) || defined(STARDENT_860) || defined(SEQUOIA) || defined(LOCUS_SYSV) || defined(SCO_386) +/* BZERO - Zero an array of bytes + * + * Function: + * + * This routine zeros an array of bytes. For some reason the VMS + * library doesn't seem to know about it. + */ +bzero(array,length) +register char *array; +register int length; +{ + PREAMBLE(); + + while(length-- > 0) *array++ = '\0'; + +}/* End Routine */ +#endif /* VMS || STARDENT et al */ + + + +#ifdef UNIX +/* CMD_OSCMD - Issue a command to the operating system + * + * Function: + * + * This routine is called in response to the EC command which allows the + * user to execute operating system commands from within the editor. + */ +int +cmd_oscmd(ct) +register struct cmd_token *ct; +{ +register char *cp; +int pid,w,status; +int pipe_desc[2]; +int last_intr_flag; +int line_cnt; +char tmpbuf[LINE_BUFFER_SIZE]; +char pipebuff[IO_BUFFER_SIZE]; +extern char susp_flag; +int pipe_buf_flag = 0; +int buf_pipe[2]; + + PREAMBLE(); + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + pipe_buf_flag = 1; + status = pipe(buf_pipe); + if (status != 0) { + error_message("?Error creating buffer pipe"); + return(FAIL); + } + }/* End IF */ + + cp = ct->ctx.carg; +/* + * Create a PIPE so that we can capture the output of the child process + */ + status = pipe(pipe_desc); + if(status != 0){ + sprintf(tmpbuf,"?Error creating PIPE: %s",error_text(errno)); + error_message(tmpbuf); + return(FAIL); + }/* End IF */ +/* + * Fork to get a process to run the shell + */ +#ifdef LINUX + if((pid = fork()) == 0){ + close(1); dup(pipe_desc[1]); + close(2); dup(pipe_desc[1]); + close(pipe_desc[0]); + close(pipe_desc[1]); + execl("/bin/bash","bash","-c",cp,0); + _exit(127); + }/* End IF */ +#else + if((pid = fork()) == 0){ + if (pipe_buf_flag) { + close(0); dup(buf_pipe[0]); + close(buf_pipe[1]); + } else { + close(pipe_desc[0]); + } + close(1); dup(pipe_desc[1]); + close(2); dup(pipe_desc[1]); + close(pipe_desc[0]); + close(pipe_desc[1]); + execl("/bin/csh","csh","-cf",cp,0); + _exit(127); + }/* End IF */ +#endif + if(pid == -1){ + close(pipe_desc[0]); + close(pipe_desc[1]); + sprintf(tmpbuf,"?Error performing FORK: %s",error_text(errno)); + return(FAIL); + }/* End IF */ + + if (pipe_buf_flag) { + struct buff_line *tmp_line = curbuf->first_line; + + close(buf_pipe[0]); + while (tmp_line) { + int n = tmp_line->byte_count; + char *lb = tmp_line->buffer; + + w = write(buf_pipe[1], lb, n); + while (w > 0) { + n -= w; + lb += w; + if (n == 0) + break; + w = write(pipe_desc[1], lb, n); + } + tmp_line = tmp_line->next_line; + } + close(buf_pipe[1]); + } + close(pipe_desc[1]); + +/* + * Loop reading stuff coming back from the pipe until we get an + * EOF which means the process has finished. Update the screen + * on newlines so the user can see what is going on. + */ + last_intr_flag = intr_flag; + line_cnt = 0; + + while((w = read(pipe_desc[0],pipebuff,sizeof(pipebuff))) > 0){ + + buff_insert(curbuf,curbuf->dot,pipebuff,w); + + if(intr_flag != last_intr_flag){ + kill(pid,SIGINT); + last_intr_flag = intr_flag; + }/* End IF */ + + if(susp_flag){ + cmd_pause(); + }/* End IF */ + + cp = pipebuff; + while(w--){ + if(intr_flag != last_intr_flag) break; + if(*cp++ == '\n' && line_cnt++ > (term_lines / 4)){ + line_cnt = 0; + if(tty_input_pending()) break; + screen_format_windows(); + screen_refresh(); + }/* End IF */ + }/* End While */ + + }/* End While */ + + close(pipe_desc[0]); + +/* + * The wait here is required so that the process we forked doesn't + * stay around as a zombie. + */ + status = 0; + while((w = wait(&status)) != pid && w != -1); + + return(SUCCESS); + +}/* End Routine */ + +/* END OF UNIX CONDITIONAL CODE */ + +#endif + + + +#ifdef VMS + +/* CMD_OSCMD - Issue a command to the operating system + * + * Function: + * + * This routine is called in response to the EC command which allows the + * user to execute operating system commands from within the editor. + */ +cmd_oscmd(ct) +register struct cmd_token *ct; +{ + PREAMBLE(); + + error_message("?VMS Does not currently support EC"); + return(FAIL); + +}/* End Routine */ + +#endif + + + +/* LOAD_QNAME_REGISTER - Loads buffer name into q-register * + * + * Function: + * + * This routine is called by a command operating on the text + * portion of a q-register if the specified q-register is *. + * In this case, we load the name of the current edit buffer + * into it so the user can easilly access it. + */ +void +load_qname_register() +{ +register struct buff_header *hbp; +register struct buff_header *qbp; + + PREAMBLE(); + + hbp = curbuf; + qbp = buff_qfind('*',1); + + if(qbp->zee > 0){ + buff_delete(qbp,0,qbp->zee); + }/* End IF */ + + buff_insert(qbp,qbp->dot,hbp->name,strlen(hbp->name)); + +}/* End Routine */ + +/* RENAME_EDIT_BUFFER - Change the name of the specified edit buffer + * + * Function: + * + * This routine is called by parser exec routines which have + * loaded q-register *. Since the text portion of this q-register + * is the buffer name, it has the effect of changing the name + * of the buffer. + */ +int +rename_edit_buffer(hbp,new_name,uct) +register struct buff_header *hbp; +register char *new_name; +register struct cmd_token *uct; +{ +register int i; +register struct undo_token *ut; +int length; + + PREAMBLE(); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_RENAME_BUFFER; + ut->carg1 = hbp->name; + + length = strlen(new_name); + hbp->name = tec_alloc(TYPE_C_CBUFF,length+1); + if(hbp->name == NULL) return(FAIL); + for(i = 0; i < length; i++){ + hbp->name[i] = new_name[i]; + }/* End FOR */ + hbp->name[length] = '\0'; + + buff_switch(hbp,0); + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_SETOPTIONS - Set runtime options via the EJ command + * + * Function: + * + * This routine is called when the user sets runtime variables using + * the EJ command. Although the command is a classic TECO command, the + * actual values are specific to Video TECO + */ +int +cmd_setoptions(arg1,arg2,uct) +int arg1; +int arg2; +struct undo_token *uct; +{ +struct undo_token fake_token; +extern int tab_width; + + PREAMBLE(); + + if(arg1 < SETOPTION_MIN_OPTION || arg1 > SETOPTION_MAX_OPTION){ + error_message("EJ option selector out of range"); + return(FAIL); + }/* End IF */ + + if(!uct) uct = &fake_token; + + switch(arg1){ + case SETOPTION_ALTERNATE_ESCAPE_CHAR: + uct->iarg1 = arg1; + uct->iarg2 = alternate_escape_character; + alternate_escape_character = arg2; + break; + case SETOPTION_SCREEN_WIDTH: + uct->iarg1 = arg1; + uct->iarg2 = forced_width; + forced_width = arg2; + screen_resize(); + break; + case SETOPTION_SCREEN_HEIGHT: + if(arg2 != 0 && arg2 < 3){ + error_message("TECO requires a larger window"); + return(FAIL); + }/* End IF */ + uct->iarg1 = arg1; + uct->iarg2 = forced_height; + forced_height = arg2; + screen_resize(); + break; + case SETOPTION_ALTERNATE_DELETE_CHAR: + uct->iarg1 = arg1; + uct->iarg2 = alternate_delete_character; + alternate_delete_character = arg2; + break; + case SETOPTION_FORCE_8_BIT_CHARS: + uct->iarg1 = arg1; + uct->iarg2 = eight_bit_output_flag; + eight_bit_output_flag = arg2 ? YES : NO; + screen_reformat_windows(); + break; + case SETOPTION_TAB_WIDTH: + uct->iarg1 = arg1; + uct->iarg2 = tab_width; + tab_width = arg2 >= 1 && arg2 <= 16 ? arg2 : NORMAL_TAB_WIDTH; + screen_reformat_windows(); + break; + case SETOPTION_HIDE_CR_CHARS: + uct->iarg1 = arg1; + uct->iarg2 = hide_cr_flag; + hide_cr_flag = arg2 ? YES : NO; + screen_reformat_windows(); + break; + + }/* End Switch */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* CMD_FTAGS - Perform UNIX tags function + * + * Function: + * + * This routine performs various unix tags functions. The following + * functions are supported: + * + * ARG1: Function: + * + * none Load tags file indicated in + * 0 (same as ) + * 1 Find tag entry which corresponds with . ARG2 + * is a flags word which says what to do with the tag + */ +int +cmd_tags(uct,arg_count,arg1,arg2,string) +struct cmd_token *uct; +int arg_count; +int arg1; +int arg2; +char *string; +{ +register struct undo_token *ut; +register struct tags *old_tags; +register struct tags *new_tags; +register struct tagent *tep = NULL; +int hashval,skip_cnt; + + PREAMBLE(); + + if(arg_count == 0) arg1 = 0; + if(arg_count < 2) arg2 = 0; + + if(arg1 < TAGS_MIN_OPTION || arg1 > TAGS_MAX_OPTION){ + error_message("FT option selector out of range"); + return(FAIL); + }/* End IF */ + + switch(arg1){ + case TAGS_LOAD_TAGS_FILE: + old_tags = current_tags; + new_tags = tag_load_file(string); + if(new_tags == NULL) return(FAIL); + current_tags = new_tags; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_LOAD_TAGS; + ut->carg1 = (char *)old_tags; + break; + case TAGS_SEARCH_TAGS_FILE: + if(current_tags == NULL) return(FAIL); + hashval = tag_calc_hash(string); + tep = current_tags->tagents[hashval]; + skip_cnt = arg2; + while(tep){ + if(strcmp(string,tep->te_symbol) == 0){ + if(skip_cnt-- <= 0){ + if(current_tags->current_entry != tep){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SELECT_TAGS; + ut->carg1 = (char *)current_tags->current_entry; + }/* End IF */ + current_tags->current_entry = tep; + return(SUCCESS); + }/* End IF */ + }/* End IF */ + tep = tep->te_next; + }/* End While */ + return(FAIL); + case TAGS_TEST_FOR_LOADED_TAGS: + if(current_tags){ + return(SUCCESS); + }/* End IF */ + return(FAIL); + case TAGS_INSERT_TARGET_FILE: + if(current_tags->current_entry){ + if(current_tags->current_entry->te_filename){ + buff_insert_with_undo( + uct, + curbuf, + curbuf->dot, + current_tags->current_entry->te_filename, + strlen(current_tags->current_entry->te_filename) + ); + return(SUCCESS); + }/* End IF */ + }/* End IF */ + return(FAIL); + case TAGS_INSERT_SEARCH_STRING: + if(current_tags->current_entry){ + if(current_tags->current_entry->te_search_string){ + buff_insert_with_undo( + uct, + curbuf, + curbuf->dot, + current_tags->current_entry->te_search_string, + strlen(current_tags->current_entry->te_search_string) + ); + return(SUCCESS); + }/* End IF */ + }/* End IF */ + return(FAIL); + case TAGS_INSERT_LINENO: + if(current_tags->current_entry){ + if(current_tags->current_entry->te_lineno){ + buff_insert_with_undo( + uct, + curbuf, + curbuf->dot, + current_tags->current_entry->te_lineno, + strlen(current_tags->current_entry->te_lineno) + ); + return(SUCCESS); + }/* End IF */ + }/* End IF */ + return(FAIL); + case TAGS_INSERT_ALL: + if(current_tags){ + tag_dump_database(current_tags,uct); + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End Switch */ + + return(SUCCESS); + +}/* End Routine */ + +/* TAG_LOAD_FILE - Read in a tags file + * + * Function: + * + * This is a huge monolithic nasty routine which reads either VI or + * EMACS tags files, and builds an internal representation. + */ +struct tags * +tag_load_file(string) +register char *string; +{ +FILE *fd = NULL; +register struct tags *tp = NULL; +register struct tagent *tep = NULL; +register char *cp; +char *outcp = NULL; +char *filename_cp = NULL; +int state = 0; +int hashval; +int c; +char emacs_flag = NO; +char comma_seen = 0; +char tmp_buffer[LINE_BUFFER_SIZE]; +char tmp_search_string[LINE_BUFFER_SIZE]; +char tmp_filename[LINE_BUFFER_SIZE]; +char tmp_symbol[LINE_BUFFER_SIZE]; +char tmp_number[LINE_BUFFER_SIZE]; +char tmp_message[LINE_BUFFER_SIZE]; +int line = 0; + + fd = fopen(string,"r"); + if(fd == NULL){ + sprintf( + tmp_message, + "Could not open TAGS file <%s>: %s", + string, + error_text(errno) + ); + error_message(tmp_message); + return(NULL); + }/* End IF */ + + tp = (struct tags *)tec_alloc(TYPE_C_TAGS,sizeof(struct tags)); + if(tp == NULL) return(NULL); + bzero((char *)tp,sizeof(struct tags)); + +/* + * Loop reading tag entries + */ + while(1){ + cp = tmp_buffer; + bzero(cp,sizeof(tmp_buffer)); + if(fgets(cp,sizeof(tmp_buffer),fd) == NULL) break; + line++; +/* + * Ok, we have the start of an entry. Check for continuation. + */ + while(cp[0]){ + if(cp[0] == '\n'){ + cp[1] = '\0'; + break; + }/* End IF */ + if(cp[0] == '\\' && cp[1] == '\n'){ + if(fgets(cp,sizeof(tmp_buffer)-(cp-tmp_buffer),fd) == NULL){ + cp[0] = '\n'; cp[1] = '\0'; + }/* End IF */ + line++; + }/* End IF */ + cp++; + }/* End While */ + +/* + * First step is to get the name of the symbol so that we can obtain the + * hash value + */ + cp = tmp_buffer; + + if(emacs_flag == NO) state = 0; + + while(*cp){ + if(state < 0) break; + switch(state){ +/* + * Skip over symbol name until we hit whitespace. Then calculate the hash + * value for that symbol, allocate the tag entry structure, and copy the + * symbol name into it. + */ + case 0: + if(cp[0] == '\f' && cp[1] == '\n'){ + emacs_flag = YES; + state = 100; + continue; + }/* End IF */ + + if(isspace((int)*cp)){ + c = *cp; + *cp = '\0'; + hashval = tag_calc_hash(tmp_buffer); + tep = (struct tagent *)tec_alloc( + TYPE_C_TAGENT, + sizeof(struct tagent) + ); + if(tep == NULL) goto err; + bzero((char *)tep,sizeof(*tep)); + + tep->te_next = tp->tagents[hashval]; + tp->tagents[hashval] = tep; + + tep->te_symbol = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(tmp_buffer) + 1 + ); + if(tep->te_symbol == NULL) goto err; + strcpy(tep->te_symbol,tmp_buffer); + + *cp = c; + state++; + }/* End IF */ + cp++; + continue; +/* + * Skip any amount of whitespace which seperates the symbol from the + * filename. + */ + case 1: + if(!isspace((int)*cp)){ + filename_cp = cp; + state++; + continue; + }/* End IF */ + cp++; + continue; +/* + * Find the length of the filename portion and copy it to the tagent + */ + case 2: + if(isspace((int)*cp)){ + c = *cp; + *cp = '\0'; + + tep->te_filename = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(filename_cp) + 1 + ); + if(tep->te_filename == NULL) goto err; + strcpy(tep->te_filename,filename_cp); + + *cp = c; + state++; + }/* End IF */ + cp++; + continue; +/* + * Skip any amount of whitespace which seperates the filename from the + * search string + */ + case 3: + if(!isspace((int)*cp)){ + state++; + continue; + }/* End IF */ + cp++; + continue; + +/* + * The next character should be a slash to begin the search string + */ + case 4: + if(*cp == '/'){ + outcp = tmp_search_string; + *outcp = '\0'; + state++; + } + cp++; + continue; +/* + * Find the length of the search string portion and copy it to the tagent + */ + case 5: + if(*cp == '/'){ + *outcp = '\0'; + + tep->te_search_string = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(tmp_search_string) + 1 + ); + if(tep->te_search_string == NULL) goto err; + strcpy(tep->te_search_string,tmp_search_string); + + state = -1; + continue; + }/* End IF */ + if(*cp == '\\'){ + state = state + 1; + cp++; + continue; + }/* End IF */ + *outcp++ = *cp++; + continue; + +/* + * Here on a backquote character. Just skip it and pass through the quoted + * character without testing it for termination of the search string. + */ + case 6: + *outcp++ = *cp++; + state = state - 1; + continue; + +/* + * Here if we encounter a form-feed. This is a giveaway that we have an + * emacs style tags file, so we have to parse it differently. + */ + case 100: + if(cp[0] != '\f' || cp[1] != '\n'){ + cp++; + continue; + }/* End IF */ + cp += 2; + state = 101; + outcp = tmp_filename; + comma_seen = 0; + continue; +/* + * Here we get the name of the source file + */ + case 101: + if(*cp == '\n'){ + if(comma_seen == 0){ + state = 100; + continue; + }/* End IF */ + outcp--; + while(*outcp != ',') outcp--; + *outcp = '\0'; + cp++; + state = 102; + outcp = tmp_symbol; + *outcp++ = 127; + continue; + }/* End IF */ + + if(*cp == ',') comma_seen = 1; + *outcp++ = *cp++; + continue; +/* + * Now we get symbol names + */ + case 102: + if(*cp == '\f'){ + state = 100; + continue; + }/* End IF */ + + if(*cp != 127){ + *outcp++ = *cp++; + continue; + }/* End IF */ +/* + * Here on a 127 code which terminates the symbol name. First eat back to + * the first symbol character. + */ + while((c = outcp[-1])){ + if(c == 127) break; + if( + isalnum(c) || + isdigit(c) || + c == '_' || + c == '$' || + c == '.' + ){ + *outcp = '\0'; + break; + }/* End IF */ + outcp--; + }/* End While */ +/* + * Now go back until we find the first non-symbol character. + */ + while((c = outcp[-1])){ + if(c == 127) break; + if( + isalnum(c) || + isdigit(c) || + c == '_' || + c == '$' || + c == '.' + ){ + outcp--; + continue; + }/* End IF */ + break; + }/* End While */ + + hashval = tag_calc_hash(outcp); + tep = (struct tagent *)tec_alloc( + TYPE_C_TAGENT, + sizeof(struct tagent) + ); + if(tep == NULL) goto err; + bzero((char *)tep,sizeof(*tep)); + + tep->te_next = tp->tagents[hashval]; + tp->tagents[hashval] = tep; + + tep->te_symbol = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(outcp) + 1 + ); + if(tep->te_symbol == NULL) goto err; + strcpy(tep->te_symbol,outcp); + tep->te_filename = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(tmp_filename) + 1 + ); + if(tep->te_filename == NULL) goto err; + strcpy(tep->te_filename,tmp_filename); + + state = 103; + continue; + +/* + * Eat until we reach a number, which is the line number + */ + case 103: + if(isdigit((int)*cp)){ + state = 104; + outcp = tmp_number; + continue; + }/* End IF */ + + if(*cp == '\f'){ + state = 100; + continue; + }/* End IF */ + + cp++; + continue; +/* + * Read in the line number + */ + case 104: + if(isdigit((int)*cp)){ + *outcp++ = *cp++; + continue; + }/* End IF */ + + *outcp++ = '\0'; + tep->te_lineno = (char *)tec_alloc( + TYPE_C_TAGSTR, + strlen(tmp_number) + 1 + ); + if(tep->te_lineno == NULL) goto err; + strcpy(tep->te_lineno,tmp_number); + + state = 105; + continue; +/* + * Eat until end of line + */ + case 105: + if(*cp == '\n'){ + cp++; + state = 102; + outcp = tmp_symbol; + continue; + }/* End IF */ + if(*cp == '\f'){ + state = 100; + continue; + }/* End IF */ + cp++; + continue; + }/* End Switch */ + }/* End While */ + + }/* End While */ + + fclose(fd); + return(tp); + +err: + + tag_free_struct(tp); + if(fd) fclose(fd); + return(NULL); + +}/* End Routine */ + +void +tag_free_struct(tp) +register struct tags *tp; +{ +register struct tagent **tepp; +register struct tagent *tep; +register int i; + + if(tp == NULL) return; + + for(i = 0, tepp = &tp->tagents[0]; i < ELEMENTS(tp->tagents); i++,tepp++){ + while((tep = *tepp)){ + *tepp = tep->te_next; + + if(tep->te_symbol){ + tec_release(TYPE_C_TAGSTR,tep->te_symbol); + }/* End IF */ + + if(tep->te_filename){ + tec_release(TYPE_C_TAGSTR,tep->te_filename); + }/* End IF */ + + if(tep->te_search_string){ + tec_release(TYPE_C_TAGSTR,tep->te_search_string); + }/* End IF */ + + tec_release(TYPE_C_TAGENT,(char *)tep); + + }/* End While */ + + }/* End FOR */ + + tec_release(TYPE_C_TAGS,(char *)tp); + +}/* End Routine */ + +int +tag_calc_hash(string) +register char *string; +{ +register int hash = 0; +register int c; +register int shift = 0; + + while((c = *string++)){ + shift = shift == 8 ? 0 : shift + 1; + hash += c << shift; + }/* End While */ + + return(hash % TAG_HASH_ENTRIES); + +}/* End Routine */ + +void +tag_dump_database(tp,uct) +register struct tags *tp; +struct cmd_token *uct; +{ +register struct tagent **tepp; +register struct tagent *tep; +register int i; +char tmp_output[LINE_BUFFER_SIZE]; + + if(tp == NULL) return; + + for(i = 0, tepp = &tp->tagents[0]; i < ELEMENTS(tp->tagents); i++,tepp++){ + tep = *tepp; + while(tep){ + + sprintf( + tmp_output, + "sym <%s> hash %d file <%s> line <%s> search <%s>\n", + tep->te_symbol ? tep->te_symbol : "", + tep->te_symbol ? tag_calc_hash(tep->te_symbol) : 0, + tep->te_filename ? tep->te_filename : "", + tep->te_lineno ? tep->te_lineno : "", + tep->te_search_string ? tep->te_search_string : "" + ); + + buff_insert_with_undo( + uct, + curbuf, + curbuf->dot, + tmp_output, + strlen(tmp_output) + ); + + tep = tep->te_next; + + }/* End While */ + }/* End FOR */ + + return; + +}/* End Routine */ diff --git a/tecdebug.c b/tecdebug.c new file mode 100644 index 0000000..1d06808 --- /dev/null +++ b/tecdebug.c @@ -0,0 +1,401 @@ +char *tecdebug_c_version = "tecdebug.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecdebug.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecdebug.c + * Debugging routines for Teco + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + extern struct screen_line *saved_screen; + extern char saved_screen_valid; + extern int term_lines; + + extern struct buff_header *buffer_headers; + extern struct buff_header *curbuf; + extern struct buff_header *qregister_push_down_list; + + extern struct format_line *format_line_free_list; + extern struct format_line *format_line_alloc_list; + + extern char format_lines_invalid; + +void +do_preamble_checks() +{ + tecdebug_check_screen_magic(); + tecdebug_check_buffer_magic(); + tecdebug_check_line_magic(); + tecdebug_check_format_magic(); + tecdebug_check_companion_pointers(); + tecdebug_check_window_pointers(); +} + +void +do_return_checks() +{ + do_preamble_checks(); +} + +/* TECDEBUG_CHECK_SCREEN_MAGIC - Check for matching magic numbers + * + * Function: + * + * This routine checks that each saved screen array element has + * the correct magic number. + */ +void +tecdebug_check_screen_magic() +{ +register int i; +register struct screen_line *sbp; +/* + * Basic checks first - check that each saved screen structure has the proper + * magic number in it. + */ + if(saved_screen_valid){ + for(sbp = saved_screen,i = 0; i < term_lines && sbp != NULL;i++,sbp++){ + if(sbp->scr_magic != MAGIC_SCREEN){ + restore_tty(); + fprintf( + stderr, + "?saved_screen[%d] bad magic#, 0x%08x should be 0x%08x\n", + i, + sbp->scr_magic, + (int)MAGIC_SCREEN + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End FOR */ + }/* End IF */ +}/* End Routine */ + + + +/* TECDEBUG_CHECK_BUFFER_MAGIC - Check for matching magic numbers + * + * Function: + * + * This routine checks that each buffer header to check for + * the correct magic number. + */ +void +tecdebug_check_buffer_magic() +{ +register struct buff_header *bp; +register char saw_curbuf; +register int count; + + bp = buffer_headers; + count = 0; + saw_curbuf = 0; + while(bp){ + if(bp == curbuf) saw_curbuf = 1; + if(bp->buf_magic != MAGIC_BUFFER){ + restore_tty(); + fprintf( + stderr, + "?buff_headers[%d] bad magic#, 0x%08x should be 0x%08x\n", + count, + bp->buf_magic, + (int)MAGIC_BUFFER + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + count += 1; + bp = bp->next_header; + }/* End While */ + + if(curbuf != NULL && saw_curbuf == 0){ + restore_tty(); + fprintf( + stderr, + "?curbuf 0x%x not on buff_headers list\n", + (unsigned int)curbuf + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + + bp = qregister_push_down_list; + count = 0; + while(bp){ + if(bp->buf_magic != MAGIC_BUFFER){ + restore_tty(); + fprintf( + stderr, + "?qpushdown[%d] bad magic#, 0x%08x should be 0x%08x\n", + count, + bp->buf_magic, + (int)MAGIC_BUFFER + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + count += 1; + bp = bp->next_header; + }/* End While */ +}/* End Routine */ + + + +/* TECDEBUG_CHECK_LINE_MAGIC - Check for matching magic numbers + * + * Function: + * + * This routine checks that each line buffer data structure for + * the correct magic number. + */ +void +tecdebug_check_line_magic() +{ +register struct buff_header *bp; +register struct buff_line *lbp; +register int count; + + bp = buffer_headers; + while(bp){ + lbp = bp->first_line; + count = 0; + while(lbp){ + if(lbp->lin_magic != MAGIC_LINE){ + restore_tty(); + fprintf( + stderr, + "?buf[%s] line %d bad magic#, 0x%08x should be 0x%08x\n", + bp->name, + count, + lbp->lin_magic, + (int)MAGIC_LINE + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + count += 1; + lbp = lbp->next_line; + }/* End While */ + bp = bp->next_header; + }/* End While */ + + bp = qregister_push_down_list; + while(bp){ + lbp = bp->first_line; + count = 0; + while(lbp){ + if(lbp->lin_magic != MAGIC_LINE){ + restore_tty(); + fprintf( + stderr, + "?qreg pdl line %d bad magic#, 0x%08x should be 0x%08x\n", + count, + lbp->lin_magic, + (int)MAGIC_LINE + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + count += 1; + lbp = lbp->next_line; + }/* End While */ + bp = bp->next_header; + }/* End While */ +}/* End Routine */ + + + +/* TECDEBUG_CHECK_FORMAT_MAGIC - Check for matching magic numbers + * + * Function: + * + * This routine checks each screen format buffer data structure for + * the correct magic number. + */ +void +tecdebug_check_format_magic() +{ +register struct format_line *fp; +register int count; + + fp = format_line_alloc_list; + while(fp){ + if(fp->fmt_magic != MAGIC_FORMAT){ + restore_tty(); + fprintf( + stderr, + "?format line bad magic#, 0x%08x should be 0x%08x\n", + fp->fmt_magic, + (int)MAGIC_FORMAT + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + fp = fp->fmt_next_alloc; + }/* End While */ + + fp = format_line_free_list; + count = 0; + while(fp){ + if(fp->fmt_magic != MAGIC_FORMAT_LOOKASIDE){ + restore_tty(); + fprintf( + stderr, + "?format lookaside[%d] bad magic#, 0x%08x should be 0x%08x\n", + count, + fp->fmt_magic, + (int)MAGIC_FORMAT_LOOKASIDE + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + fp = fp->fmt_next_line; + count += 1; + }/* End While */ + +}/* End Routine */ + + + +/* TECDEBUG_CHECK_COMPANION_POINTERS - Check for pointer consistency + * + * Function: + * + * This routine checks that each saved_screen entry points to + * a single format line which also points back. It's an error + * if a structure points to another structure which doesn't + * point back. + */ +void +tecdebug_check_companion_pointers() +{ +register int i; +register struct screen_line *sbp; +register struct format_line *fbp; + +/* + * First check from the saved screen side that the pointers seem to be + * consistent - i.e. that every structure points to one and only one + * structure which points back. + */ + if(!saved_screen_valid) return; + for(sbp = saved_screen,i = 0; i < term_lines && sbp != NULL;i++,sbp++){ + if(sbp->companion == NULL) continue; + if(sbp->companion->fmt_saved_line != sbp){ + restore_tty(); + fprintf( + stderr, + "?saved_screen[%d] companion 0x%08x ->fmt_saved_line 0x%08x\n", + i, + (unsigned int)sbp->companion, + (unsigned int)sbp->companion->fmt_saved_line + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End FOR */ + +/* + * Now check from the allocated format structure side of things, to make sure + * that each one of them which points to a screen structure is pointed back + * at by that screen structure. + */ + fbp = format_line_alloc_list; + while(fbp){ + if(fbp->fmt_saved_line && fbp->fmt_saved_line->companion != fbp){ + restore_tty(); + fprintf( + stderr, + "?fbp 0x%08x fbp->fmt_saved 0x%08x ->companion 0x%08x\n", + (unsigned int)fbp, + (unsigned int)fbp->fmt_saved_line, + (unsigned int)fbp->fmt_saved_line->companion + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + fbp = fbp->fmt_next_alloc; + }/* End While */ + +}/* End Routine */ + + + +/* TECDEBUG_CHECK_WINDOW_POINTERS - Check for window pointer consistency + * + * Function: + * + * This routine checks that every format line is correctly chained with respect + * to which window it is connected to. An error occurs if the line_buffer does + * not chain down to it. Also, for every "next_window" pointer, all format + * structures chained off of it must belong to the same window. + */ +void +tecdebug_check_window_pointers() +{ +register struct format_line *fbp,*fl_win_head,*fl_temp; +register struct window *wptr; +register struct buff_line *blp; +char saw_our_format_line; + + if(format_lines_invalid) return; + +/* + * Check each format line on the allocated list + */ + for(fbp = format_line_alloc_list; fbp != NULL; fbp = fbp->fmt_next_alloc){ + saw_our_format_line = 0; + blp = fbp->fmt_buffer_line; + fl_win_head = blp->format_line; + for(; fl_win_head != NULL; fl_win_head = fl_win_head->fmt_next_window){ + wptr = fl_win_head->fmt_window_ptr; + for(fl_temp = fl_win_head; fl_temp != NULL; fl_temp = fl_temp->fmt_next_line){ + if(fl_temp == fbp) saw_our_format_line = 1; + if(fl_temp->fmt_window_ptr != wptr){ + restore_tty(); + fprintf( + stderr, + "?wrong window in fmt chain format_line 0x%08x format_line->wptr 0x%08x wptr 0x%08x\n", + (unsigned int)fl_temp, + (unsigned int)fl_temp->fmt_window_ptr, + (unsigned int)wptr + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + if(fl_temp->fmt_buffer_line != blp){ + restore_tty(); + fprintf( + stderr, + "?wrong buffer_line in fmt chain fmt 0x%08x fmt->fmt_buffer_line 0x%08x lbp 0x%08x\n", + (unsigned int)fl_temp, + (unsigned int)fl_temp->fmt_buffer_line, + (unsigned int)blp + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End FOR */ + }/* End FOR */ + if(saw_our_format_line == 0){ + restore_tty(); + fprintf( + stderr, + "?never encountered our format line 0x%08x in format list\n", + (unsigned int)fbp + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End FOR */ + +}/* End Routine */ diff --git a/tecdisp.c b/tecdisp.c new file mode 100644 index 0000000..a1d2bb6 --- /dev/null +++ b/tecdisp.c @@ -0,0 +1,2867 @@ +char *tecdisp_c_version = "tecdisp.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecdisp.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecdisp.c + * Terminal Screen Display Subroutines + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +/* + * Global Storage is defined here for lack of a better place. + */ +/* + * External Variables + */ + extern char teco_startup; + extern char screen_startup; + extern int term_columns; + extern int term_lines; + extern int forced_height,forced_width; + extern int tty_input_chan; + extern int tty_output_chan; + extern char insert_delete_line_capability; + extern char eight_bit_output_flag; + extern char hide_cr_flag; + extern char tab_expand[]; + extern int tab_width; + +#ifdef TERMCAP + extern int termcap_sg; +#endif + +#ifdef TERMINFO + extern int terminfo_magic_cookie_glitch; +#endif + +/* + * Global Variables + */ + int curx; + int cury; + short cur_mode; + int cursor_x; + int cursor_y; + int screen_sequence; + int echo_cnt; + char ring_audible_bell; + int buffer_li; + int buffer_half_li; + char format_lines_invalid; + + struct format_line * + allocate_format_buffer(struct window *,struct buff_line *); + struct format_line * + screen_find_window_format_line(struct window *,struct buff_line *); + struct window *create_window(int,int); + void screen_optimize_lines(void); + void screen_optimize_by_pattern_matching(void); + void screen_gc_format_lines(void); + void screen_free_window_format_lines(struct window *,struct buff_line *); + void screen_account_for_delete_line(int y, int count); + void screen_account_for_insert_line(int y, int count); + int screen_rebuild_from_scratch(struct window *wptr); + int screen_format_buff_line(struct window *wptr,struct buff_line *lbp); + int screen_find_dot(struct window *wptr); + +/* + * Global structures + */ + struct screen_line *saved_screen; + char saved_screen_valid; + + int next_window_number = 1; + struct window *curwin; + struct window *window_list; + struct format_line message_line; + char message_flag = NO; + struct format_line echo_line; + + struct format_line *format_line_free_list = NULL; + struct format_line *format_line_alloc_list = NULL; + +/* + * External Routines, etc. + */ + extern struct buff_header *curbuf; + extern struct buff_header *buffer_headers; + + extern int term_speed; + extern char input_pending_flag; + extern char resize_flag; + +/* SCREEN_INIT - Initialize the TECO screen package + * + * Function: + * + * This entry point gives the screen package a chance to initialize + * itself. + */ +int +screen_init() +{ +register int i,j; +register struct window *wptr; +register struct screen_line *lp; +register short *sp; + + PREAMBLE(); + +/* + * Call the terminal handling routines, and give them a chance to set + * the terminal up. + */ + term_init(); + +/* + * Set the cursor to the upper left of the display and perform a clear to end + * of display command. This has the effect of clearing the entire display. + */ + term_goto(0,0); + term_clrtobot(); + +/* + * If this is a restart (from ^Z most likely), then that is all we have to do. + * The rest of this is only to initialize data structures the first time + * through + */ + if(screen_startup == NO) return(SUCCESS); + screen_startup = NO; + +/* + * Calculate how many lines are available for display of the edit buffer. + */ + buffer_li = term_lines - SCREEN_RESERVED_LINES; + buffer_half_li = ((buffer_li + 1) & ~1) / 2; + +/* + * Allocate memory for the saved screen structure. This is used to remember + * what is currently showing on the terminal. Since we just cleared the screen, + * we set the current contents of this to be all spaces (i.e., blank). + */ + i = sizeof(struct screen_line) * term_lines; + saved_screen = (struct screen_line *)tec_alloc(TYPE_C_SCR,i); + if(saved_screen == NULL) return(FAIL); + + bzero(saved_screen,i); + screen_sequence = 1; + + for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ + lp->scr_magic = MAGIC_SCREEN; + lp->companion = NULL; + lp->sequence = 0; + lp->buffer = (short *) + tec_alloc(TYPE_C_SCRBUF,(int)(sizeof(short) * term_columns)); + sp = lp->buffer; + for(j = 0; j < term_columns; j++,sp++) *sp = ' '; + }/* End FOR */ + + saved_screen_valid = 1; + +/* + * Allocate the initial window structure + */ + window_list = wptr = + create_window(term_columns,term_lines - SCREEN_RESERVED_LINES); + curwin = wptr; + wptr->win_buffer = curbuf; + + lp = saved_screen; + lp += term_lines - SCREEN_RESERVED_LINES - 1; + wptr->win_label_line.fmt_saved_line = lp; + lp->companion = &wptr->win_label_line; +/* + * Now we set up the structures for our special lines which form the bottom + * of the display screen. + */ + echo_line.fmt_buffer_line = message_line.fmt_buffer_line = NULL; + echo_line.fmt_buffer_size = message_line.fmt_buffer_size = term_columns; + + echo_line.fmt_buffer = (short *) + tec_alloc( + TYPE_C_SCRBUF, + (int)(sizeof(short) * echo_line.fmt_buffer_size) + ); + message_line.fmt_buffer = (short *) + tec_alloc( + TYPE_C_SCRBUF, + (int)(sizeof(short)*message_line.fmt_buffer_size) + ); + + for(i = 0; i < term_columns; i++){ + message_line.fmt_buffer[i] = ' '; + echo_line.fmt_buffer[i] = ' '; + }/* End FOR */ + + echo_line.fmt_buffer[0] = '*'; + echo_line.fmt_sequence = message_line.fmt_sequence = 1; + echo_line.fmt_next_line = message_line.fmt_next_line = NULL; + + message_line.fmt_visible_line_position = + echo_line.fmt_visible_line_position = -1; + + lp = saved_screen; + lp += term_lines - SCREEN_RESERVED_LINES; + + message_line.fmt_permanent = 1; + message_line.fmt_saved_line = lp; + message_line.fmt_magic = MAGIC_FORMAT_LOOKASIDE; + lp->companion = &message_line; + + lp += 1; + echo_line.fmt_permanent = 1; + echo_line.fmt_saved_line = lp; + echo_line.fmt_magic = MAGIC_FORMAT_LOOKASIDE; + lp->companion = &echo_line; + + screen_label_line(curbuf," TECO",LABEL_C_TECONAME); + + return(SUCCESS); + +}/* End Routine */ + + + +/* SCREEN_RESIZE - Cause the screen to be refreshed at a new size + * + * Function: + * + * This function is called when the size of the terminal screen has + * changed. This is common on a windowing terminal. This routine has + * to clean up the existing screen database and re-initialize it. + */ +void +screen_resize() +{ +register int i; +register struct window *wptr; +register struct screen_line *lp; + +#ifdef SUN_STYLE_WINDOW_SIZING + struct winsize os_window; +#endif + + PREAMBLE(); + +/* + * The screen_gc_format_line is going to scavenge all the lines because + * we're making them look not-used here. However, we still go ahead and + * clobber the line_buffer's pointer, because we don't like the idea of + * having hanging pointers pointed at our screen lines. + */ + if(saved_screen_valid){ + for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ + if(lp->companion){ + if(lp->companion->fmt_saved_line != lp){ + fprintf( + stderr, + "lp->companion 0x%x != ->fmt_saved_line 0x%x\n", + (unsigned int)lp->companion, + (unsigned int)lp->companion->fmt_saved_line + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + lp->companion->fmt_saved_line = NULL; + lp->companion = NULL; + }/* End IF */ + }/* End FOR */ + }/* End IF */ + + screen_gc_format_lines(); + +/* + * Return the memory used for the saved screen structure. This structure + * is the one used to remember what is currently showing on the terminal. + */ + if(saved_screen_valid){ + saved_screen_valid = 0; + for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ + if(lp->buffer) tec_release(TYPE_C_SCRBUF,(char *)lp->buffer); + lp->buffer = NULL; + }/* End FOR */ + + if(saved_screen) tec_release(TYPE_C_SCR,(char *)saved_screen); + saved_screen = NULL; + + }/* End IF */ + +/* + * Reclaim all the window structures + */ + while(1){ + wptr = window_list; + if(wptr == NULL) break; + window_list = wptr->win_next_window; +/* + * Release any memory used to hold the strings in the label line + */ + for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ + if(wptr->win_label_field_contents[i]){ + tec_release( + TYPE_C_LABELFIELD, + wptr->win_label_field_contents[i] + ); + wptr->win_label_field_contents[i] = NULL; + }/* End IF */ + }/* End FOR */ +/* + * Release the buffer used to hold formatted data + */ + tec_release(TYPE_C_SCRBUF,(char *)wptr->win_label_line.fmt_buffer); + tec_release(TYPE_C_WINDOW,(char *)wptr); + + }/* End While */ + + window_list = NULL; + + if(echo_line.fmt_buffer){ + tec_release(TYPE_C_SCRBUF,(char *)echo_line.fmt_buffer); + }/* End IF */ + + if(message_line.fmt_buffer){ + tec_release(TYPE_C_SCRBUF,(char *)message_line.fmt_buffer); + }/* End IF */ + echo_cnt = 0; + + echo_line.fmt_buffer_line = message_line.fmt_buffer_line = NULL; + echo_line.fmt_buffer_size = message_line.fmt_buffer_size = 0; + +/* + * If the OS supports window size ioctls, we try for that since it + * will tend to be more correct for a window environment. + */ +#ifdef SUN_STYLE_WINDOW_SIZING + if(ioctl(tty_output_chan,TIOCGWINSZ,&os_window) >= 0){ + term_lines = os_window.ws_row; + term_columns = os_window.ws_col; + }/* End IF */ +#endif + if(forced_height > 0) term_lines = forced_height; + if(forced_width > 0) term_columns = forced_width; + + if(term_lines == 0) term_lines = 24; + if(term_columns == 0) term_columns = 80; + + resize_flag = NO; + screen_startup = YES; + screen_init(); + buff_switch(curbuf,0); + parser_reset_echo(); + screen_echo('\0'); + screen_format_windows(); + +}/* End Routine */ + + + +/* SCREEN_FINISH - Here when we are exiting the editor + * + * Function: + * + * This routine is called when TECO is being exited to give us a chance + * to cleanly leave the screen package. We put the cursor at the bottom + * of the screen, send any termination escape sequences that are required + * and flush it all out. + */ +void +screen_finish() +{ + + term_finish(); + +}/* End Routine */ + + + +/* SCREEN_REFRESH - Redraw the screen based on new database + * + * Function: + * + * This routine gets called after the new companion entries have been set + * up for the saved_screen database. Therefore, we can run through the + * lines and update only those which have changed. + */ +void +screen_refresh() +{ +register int x,y; +register struct screen_line *lp; +register struct format_line *his_lp; +register short *sp,*osp; +char pos_flag; + + PREAMBLE(); + + input_pending_flag = tty_input_pending(); + if(input_pending_flag == YES) return; + +/* + * First try to optimize output by performing insert/delete line operations + */ + if(insert_delete_line_capability){ + screen_optimize_lines(); + }/* End IF */ +/* + * Go through each line and insure that the screen appearence is changed to + * match those described by the line buffer structures. + */ + for(y = 0; y < term_lines; y++){ + if(term_speed <= 2400){ + term_flush(); + }/* End IF */ + + input_pending_flag = tty_input_pending(); + if(input_pending_flag == YES) break; + +/* + * Get pointers to the saved screen entry which tells us what the screen + * currently looks like, and pointers to the line buffer structure which + * tells us what the screen ought to look like when we are done. + */ + lp = saved_screen + y; + osp = lp->buffer; + his_lp = lp->companion; +/* + * If there is no line buffer structure, the line ought to appear blank on + * the screen. If it already is blank, no sweat, otherwise we want to do + * a clear to end of line function. If the saved_screen sequence number + * is zero, this is a flag that the line is already clear. + */ + if(his_lp == NULL){ + if(lp->sequence == 0) continue; + for(x = 0; x < term_columns; x++,osp++){ + if((*osp & SCREEN_M_DATA) != ' '){ + if(cury != y || curx > x) term_goto(x,y); + if(cur_mode & SCREEN_M_REVERSE){ + term_standend(); + cur_mode &= ~SCREEN_M_REVERSE; + }/* End IF */ + term_clrtoeol(); + while(x < term_columns){ + *osp++ = ' '; + x += 1; + }/* End While */ + }/* End IF */ + }/* End FOR */ + lp->sequence = 0; + continue; + }/* End IF */ +/* + * If the line buffer sequence number is less than that of the saved screen + * sequence number, we know that this line has not been changed, and thus we + * don't even have to check to see whether they match. + */ + if(his_lp->fmt_sequence <= lp->sequence) continue; + lp->sequence = screen_sequence; + sp = his_lp->fmt_buffer; + his_lp->fmt_visible_line_position = y; +/* + * Well, changes have been made, so we have to go through the line on a byte + * by byte basis comparing character positions. + */ + for(x = 0; x < term_columns; x++,sp++,osp++){ +/* + * If the characters are the same we don't have to take any special action + * unless it is a newline in which case sometimes we have special things to do + */ + if((*sp & SCREEN_M_DATA) != '\n' && *osp == *sp){ + continue; + }/* End If */ +/* + * If it IS a newline, we want to make sure that the screen shows blanks + * for the rest of this line. If it does not, we have to do a clear to + * end of line operation and update the saved screen database to reflect + * this. + */ + if((*sp & SCREEN_M_DATA) == '\n'){ + for(; x < term_columns; x++,osp++){ + if(*osp == ' ') continue; + if(curx != x || cury != y) term_goto(x,y); + if(cur_mode & SCREEN_M_REVERSE){ + term_standend(); + cur_mode &= ~SCREEN_M_REVERSE; + }/* End IF */ + term_clrtoeol(); + for(; x < term_columns; x++){ + *osp++ = ' '; + }/* End FOR */ + }/* End FOR */ + break; + }/* End IF */ +/* + * Copy the byte into our database to reflect the new appearance of the screen + */ + *osp = *sp; + pos_flag = 1; +/* + * If the current position of the cursor is not the position of the character + * we want to update, we have to position to that place. If we are on the + * wrong line, or if we are already to the right of the desired position, + * we have to do a full position escape sequence. + */ + if(cury != y || curx > x){ + term_goto(x,y); + pos_flag = 0; + }/* End IF */ +/* + * If we are not positioned yet, check to see if we are only a few positions + * to the left of where we want to be. If this is the case, it is faster to + * output the actual characters than to generate a full cursor motion escape + * sequence. + */ + if(pos_flag && x - curx < 5){ + while(curx < x){ + if((his_lp->fmt_buffer[curx] & SCREEN_M_REVERSE) != + (cur_mode & SCREEN_M_REVERSE)) break; + term_putc(his_lp->fmt_buffer[curx] & SCREEN_M_DATA); + curx++; + }/* End While */ + pos_flag = 0; + }/* End IF */ +/* + * If we get to here and the position is not correct, no shortcuts helped so + * we have to output the full escape sequence to position the cursor + */ + if(curx != x || cury != y) term_goto(x,y); +/* + * Check to see that we are in the correct mode (standout or not). If not, + * we have to change it before we output the data byte + */ + if((*sp & SCREEN_M_REVERSE) != (cur_mode & SCREEN_M_REVERSE)){ + if(*sp & SCREEN_M_REVERSE){ + term_standout(); + cur_mode |= SCREEN_M_REVERSE; + }/* End IF */ + else { + term_standend(); + cur_mode &= ~SCREEN_M_REVERSE; + }/* End Else */ + }/* End IF */ +/* + * Output the data byte and record that that changes the current x position + */ + term_putc(*sp & SCREEN_M_DATA); + curx++; + + }/* End FOR */ + }/* End FOR */ + +/* + * We don't want to leave the terminal in reverse video between screen updates, + * so if we are in reverse video mode, put it back to non-reverse. + */ + if(cur_mode & SCREEN_M_REVERSE){ + term_standend(); + cur_mode &= ~SCREEN_M_REVERSE; + }/* End IF */ + +/* + * Make sure that the cursor gets left in the position that indicates the + * "dot" position in the edit buffer. + */ + if(curx != cursor_x || cury != cursor_y){ + term_goto(cursor_x,cursor_y); + }/* End IF */ + +/* + * Force this all to be written to the terminal, and increment the sequence + * number + */ + term_flush(); + screen_gc_format_lines(); + screen_sequence += 1; + +}/* End Routine */ + + + +/* SCREEN_OPTIMIZE_LINES - Optimize output with insert/delete line operations + * + * Function: + * + * This routine looks for left over mappings between our saved screen + * array and the formatted format_line structures. Old mappings can + * tell us how lines that are still going to be visible have moved. + */ +void +screen_optimize_lines() +{ +register int y; +int vlp; +register struct screen_line *lp; +int blk_delete_count; +int blk_insert_count; +int companion_count; +int offset; + + PREAMBLE(); + + companion_count = 0; + +/* + * Search for a line which is known to be out of place + */ + for(y = 0; y < buffer_li; y++){ + lp = &saved_screen[y]; + if(lp->companion == NULL) continue; + if((vlp = lp->companion->fmt_visible_line_position) == -1) continue; + if(vlp != y) break; + companion_count += 1; + break; + }/* End FOR */ + + if(y == buffer_li){ + if(companion_count == 0) screen_optimize_by_pattern_matching(); + return; + }/* End IF */ + +/* + * Search for delete line opportunities + */ + offset = 0; + for(y = 0; y < term_lines; y++){ + lp = &saved_screen[y]; + if(lp->companion == NULL) continue; + if((vlp = lp->companion->fmt_visible_line_position) == -1) continue; + vlp += offset; + if(vlp == y) continue; + + if(vlp > y){ + blk_delete_count = vlp - y; + term_delete_line(y-offset,blk_delete_count); + screen_account_for_delete_line(y-offset,blk_delete_count); + continue; + }/* End IF */ + + if(vlp < y){ + offset += y - vlp; + }/* End IF */ + + }/* End FOR */ +/* + * Search for insert line opportunities + */ + for(y = 0; y < term_lines; y++){ + lp = &saved_screen[y]; + if(lp->companion == NULL) continue; + if((vlp = lp->companion->fmt_visible_line_position) == -1) continue; + if(vlp == y) continue; + + if(vlp > y){ + void screen_message(); + screen_message("insert_line confused"); + }/* End IF */ + + if(vlp < y){ + blk_insert_count = y - vlp; + term_insert_line(vlp,blk_insert_count); + screen_account_for_insert_line(vlp,blk_insert_count); + }/* End IF */ + + }/* End FOR */ + +}/* End Routine */ + + + +/* SCREEN_ACCOUNT_FOR_DELETE_LINE - Update screen database + * + * Function: + * + * This routine is called when a delete line sequence is sent to + * the terminal. It adjusts the screen database to reflect what + * has happened to the terminal. + */ +void +screen_account_for_delete_line(y,count) +int y; +int count; +{ +register int i,x; +register struct screen_line *top_lp,*bottom_lp; +register struct format_line *slp; +register short *sp; + + PREAMBLE(); + +/* + * The result of a delete line operation will be that starting at the + * location it was issued, lines will be replaced by the contents of + * lines lower down on the screen. + */ + top_lp = &saved_screen[y]; + bottom_lp = top_lp + count; + + for(i = y + count; i < term_lines; i++,top_lp++,bottom_lp++){ + sp = top_lp->buffer; + top_lp->buffer = bottom_lp->buffer; + bottom_lp->buffer = sp; + + top_lp->sequence = bottom_lp->sequence; + }/* End FOR */ + + top_lp = saved_screen; + for(i = 0; i < term_lines; i++,top_lp++){ + if((slp = top_lp->companion) == NULL) continue; + if(slp->fmt_visible_line_position >= y){ + slp->fmt_visible_line_position -= count; + }/* End IF */ + if(slp->fmt_visible_line_position < 0){ + slp->fmt_visible_line_position = -1; + }/* End IF */ + }/* End FOR */ + + top_lp = &saved_screen[term_lines - count]; + for(i = term_lines - count; i < term_lines; i++,top_lp++){ + + top_lp->sequence = 0; + + sp = top_lp->buffer; + for(x = 0; x < term_columns; x++){ + *sp++ = ' '; + }/* End FOR */ + + }/* End FOR */ + +}/* End Routine */ + +/* SCREEN_ACCOUNT_FOR_INSERT_LINE - Update screen database + * + * Function: + * + * This routine is called when an insert line sequence is sent to + * the terminal. It adjusts the screen database to reflect what + * has happened to the terminal appearence. + */ +void +screen_account_for_insert_line(y,count) +int y; +int count; +{ +register int i,x; +register struct screen_line *top_lp,*bottom_lp; +register struct format_line *slp; +register short *sp; + + PREAMBLE(); + + top_lp = &saved_screen[term_lines - count - 1]; + bottom_lp = &saved_screen[term_lines - 1]; + + for(i = term_lines - count - 1; i >= y; i--,top_lp--,bottom_lp--){ + sp = bottom_lp->buffer; + bottom_lp->buffer = top_lp->buffer; + top_lp->buffer = sp; + + bottom_lp->sequence = top_lp->sequence; + + }/* End FOR */ + + top_lp = saved_screen; + for(i = 0; i < term_lines; i++,top_lp++){ + if((slp = top_lp->companion) == NULL) continue; + if(slp->fmt_visible_line_position >= y){ + slp->fmt_visible_line_position += count; + }/* End IF */ + if(slp->fmt_visible_line_position >= term_lines){ + slp->fmt_visible_line_position = -1; + }/* End IF */ + }/* End FOR */ + + top_lp = &saved_screen[y]; + for(i = 0; i < count; i++,top_lp++){ + + top_lp->sequence = 0; + + sp = top_lp->buffer; + for(x = 0; x < term_columns; x++){ + *sp++ = ' '; + }/* End FOR */ + }/* End FOR */ + +}/* End Routine */ + + + +/* SCREEN_OPTIMIZE_BY_PATTERN_MATCHING - More insert/delete line optimizations + * + * Function: + * + * This routine is called previous to the brute force screen repaint, + * but after the first crack at insert/delete line optimization. This + * routine looks for patterns of lines which indicate insert/delete + * line can save us some output. + */ +void +screen_optimize_by_pattern_matching() +{ +register int x,y,i,c; +register struct screen_line *lp; +register struct format_line *his_lp; +register short *sp,*osp; +char stuff_flag; +int old_check[SCREEN_MAX_LINES],new_check[SCREEN_MAX_LINES]; +int first_change,last_change; + + PREAMBLE(); + + stuff_flag = 0; + for(y = 0; y < buffer_li; y++){ + +/* + * Get pointers to the saved screen entry which tells us what the screen + * currently looks like, and pointers to the line buffer structure which + * tells us what the screen ought to look like when we are done. + */ + lp = saved_screen + y; + osp = lp->buffer; + his_lp = lp->companion; +/* + * If there is no line buffer structure, the line ought to appear blank on + * the screen. If it already is blank, no sweat, otherwise we want to do + * a clear to end of line function. + */ + new_check[y] = old_check[y] = 0; + if(his_lp && his_lp->fmt_sequence <= lp->sequence) continue; + if(his_lp == NULL && lp->sequence == 0) continue; +/* + * Calculate the checksum for the data currently showing on the screen + */ + for(x = 0; x < term_columns; x++,osp++){ + if((c = *osp & SCREEN_M_DATA) != ' '){ + old_check[y] = (old_check[y] << 1) + c; + stuff_flag = 1; + }/* End IF */ + }/* End FOR */ + + if(his_lp == NULL) continue; + sp = his_lp->fmt_buffer; +/* + * Calculate the checksum for the data which we want to be showing on the + * screen. + */ + for(x = 0; x < term_columns; x++,sp++){ + c = *sp & SCREEN_M_DATA; + if(c == ' ') continue; + if(c == '\n') break; + new_check[y] = (new_check[y] << 1) + c; + stuff_flag = 1; + }/* End FOR */ + + }/* End FOR */ + +/* + * If no changes have been made, just return + */ + if(!stuff_flag) return; + +/* + * Determine which region of the screen contains lines which have changed + */ + first_change = last_change = -1; + for(y = 0; y < buffer_li; y++){ + if(old_check[y] == new_check[y]) continue; + first_change = y; + break; + }/* End FOR */ + +/* + * If checksum arrays seem to be identical or only one line changed, then + * just return and don't attempt further optimization. + */ + if(first_change == -1) return; + + for(y = buffer_li - 1; y >= first_change; y--){ + if(old_check[y] == new_check[y]) continue; + last_change = y; + break; + }/* End FOR */ + + if(first_change == last_change) return; + +/* + * Here is the code which tries for a delete-line optimization + */ + for(i = 1; i < last_change - first_change - 3; i++){ + if(old_check[first_change + i] == new_check[first_change]){ + for(y = first_change; y < last_change - i; y++){ + if(old_check[y + i] != new_check[y]) break; + }/* End FOR */ + if(y == (last_change - i)){ + + term_delete_line(first_change,i); + screen_account_for_delete_line(first_change,i); + term_insert_line(last_change+1-i,i); + screen_account_for_insert_line(last_change+1-i,i); + + return; + + }/* End IF */ + }/* End IF */ + }/* End FOR */ + +/* + * Here is the code which tries for a insert-line optimization + */ + for(i = 1; i < last_change - first_change - 3; i++){ + if(old_check[first_change] == new_check[first_change + i]){ + for(y = first_change; y < last_change - i; y++){ + if(old_check[y] != new_check[y + i]) break; + }/* End FOR */ + + if(y == (last_change - i)){ + term_delete_line(last_change+1-i,i); + screen_account_for_delete_line(last_change+1-i,i); + term_insert_line(first_change,i); + screen_account_for_insert_line(first_change,i); + break; + }/* End IF */ + + }/* End IF */ + }/* End FOR */ + +}/* End Routine */ + + + +/* SCREEN_ECHO - Echo the character + * + * Function: + * + * This routine allows the parser to hand us input characters which need + * to be echoed. The reason this is done here instead of by the normal + * screen formatting routines is that some characters are echoed in a + * different way than they are normally displayed. The most notable + * example is newline which just indicates end of line in the normal + * buffer output, but which gets echoed as '' when it is an echo + * operation. + */ +void +screen_echo(data) +char data; +{ +register int i; + + PREAMBLE(); + +/* + * If we are getting close to the right edge of the screen, we remove the + * first 3/4 of the echo line and just redisplay the last 1/4 to make room + * for future echo characters. + */ + if(echo_cnt > (term_columns - 4)){ + for(i = 0; i < (term_columns / 4); i++){ + echo_line.fmt_buffer[ 1 + i ] = + echo_line.fmt_buffer[ 1 + echo_cnt - (term_columns / 4) + i ]; + }/* End FOR */ + echo_cnt = i; + for(; i < term_columns-2; i++){ + echo_line.fmt_buffer[ 1 + i ] = ' '; + }/* End FOR */ + }/* End IF */ + +/* + * Dispatch the byte to determine how it should be formatted. NULL is a + * special case which is used to flag the fake cursor position on the + * echo line. This is shown by outputing a reverse-video space to the + * position. + */ + switch(data){ + case '\0': + +/* + * If we are on a high-speed line, do the reverse-video space trick to + * show a non-blinking cursor. + */ + if(term_speed > 1200){ + echo_line.fmt_buffer[ 1 + echo_cnt ] = ' ' | SCREEN_M_REVERSE; + echo_line.fmt_buffer[ 2 + echo_cnt ] = ' '; + echo_line.fmt_buffer[ 3 + echo_cnt ] = '\n'; + }/* End IF */ +/* + * Otherwise, if we are on a low speed line, just insert a carriage return + * so that the rest of the line is shown as blanks. + */ + else { + echo_line.fmt_buffer[ 1 + echo_cnt ] = '\n'; + }/* End IF */ + + echo_line.fmt_sequence = screen_sequence; + return; + + case ESCAPE: + data = '$'; + break; + case '\n': + for(i = 0; i < strlen(""); i++){ + screen_echo(""[i]); + }/* End FOR */ + return; + case RUBOUT: + return; +/* + * Tabs are always echoed as a fixed length string of spaces regardless of + * the screen position since normal tab algorithms look weird on the echo + * line. + */ + case '\t': + for(i = 0; i < strlen(" "); i++){ + screen_echo(" "[i]); + }/* End FOR */ + return; + default: + break; + }/* End Switch */ + +/* + * If this is a control character, echo as ^byte + */ + if((unsigned char)data < ' '){ + screen_echo('^'); + screen_echo(data + 'A' - 1); + return; + }/* End IF */ + +/* + * Here if it is a normal printable character. We can just echo the character + * as is with no special stuff going on. + */ + echo_line.fmt_buffer[ 1 + echo_cnt++ ] = data; + echo_line.fmt_sequence = screen_sequence; + +}/* End Routine */ + +/* SCREEN_RESET_ECHO - Reset the echo line + * + * Function: + * + * This routine is called to reset the echo line according to the current + * list of command tokens. This is used after difficult things like rubout + * which would be very difficult to back out of. + */ +void +screen_reset_echo(ct) +register struct cmd_token *ct; +{ + + PREAMBLE(); + + echo_line.fmt_buffer[ 1 + echo_cnt ] = ' '; + + while(echo_cnt){ + echo_cnt -= 1; + echo_line.fmt_buffer[ 1 + echo_cnt ] = ' '; + }/* End While */ + + while(ct){ + if(ct->opcode == TOK_C_INPUTCHAR){ + screen_echo(ct->input_byte); + }/* End IF */ + ct = ct->next_token; + }/* End While */ + + echo_line.fmt_buffer[ 1 + echo_cnt ] = '\n'; + + echo_line.fmt_sequence = screen_sequence; + +}/* End Routine */ + + + +/* SCREEN_MESSAGE - Place a message in the message line + * + * Function: + * + * This routine allows the parser to hand us messages which need to be + * displayed. These can be error messages generated by the parse, or + * they can be output messages from a user generated with the ^A command. + */ +void +screen_message(string) +register char *string; +{ +register short *sp; +register int i; +register int c = 0; +register char *mep; +char multi_echo_buffer[32]; + + PREAMBLE(); + + message_flag = YES; + sp = message_line.fmt_buffer; + i = term_columns; + mep = ""; + + while(*mep || *string){ + while(*mep){ + *sp++ = c = *mep++; + if(i-- <= 0) goto too_wide; + continue; + }/* End While */ + + c = *string++; + + switch(c){ +// case NULL: + case 0: + goto too_wide; + case ESCAPE: + c = '$'; + break; + case '\n': + mep = ""; + continue; + case RUBOUT: + mep = ""; + continue; + case '\t': + mep = " "; + continue; + }/* End Switch */ + + if((unsigned char)c < ' '){ + multi_echo_buffer[0] = '^'; + multi_echo_buffer[1] = c + 'A' - 1; + multi_echo_buffer[2] = '\0'; + mep = multi_echo_buffer; + continue; + }/* End IF */ + + *sp++ = c; + if(i-- <= 0) goto too_wide; + }/* End While */ + +too_wide: + if(c != '\n' && i > 0) *sp++ = '\n'; + message_line.fmt_sequence = screen_sequence; + +}/* End Routine */ + +/* ERROR_MESSAGE - Place an error message in the message line + * + * Function: + * + * This routine allows the parser to hand us error messages + * which need to be displayed. In addition, the BELL character + * is output to get the humans attention. + */ +void +error_message(string) +char *string; +{ + PREAMBLE(); + + screen_message(string); + ring_audible_bell = 1; + +}/* End Routine */ + +/* SCREEN_RESET_MESSAGE - Reset the message line + * + * Function: + * + * This routine is called to reset the message line to null + */ +void +screen_reset_message() +{ +register int i; +register short *sp; + + PREAMBLE(); + + if(message_flag == NO) return; + message_flag = NO; + sp = message_line.fmt_buffer; + + for(i = 0; i < term_columns; i++){ + *sp++ = ' '; + }/* End FOR */ + + message_line.fmt_sequence = screen_sequence; + +}/* End Routine */ + +/* SCREEN_SAVE_CURRENT_MESSAGE - Copy out the current message + * + * Function: + * + * This routine copies out the current message up to a certain length. + */ +void +screen_save_current_message(message_save_buff,message_save_max_length) +char *message_save_buff; +int message_save_max_length; +{ +int i; +short *sp; + + PREAMBLE(); + + sp = message_line.fmt_buffer; + i = term_columns; + if(i >= message_save_max_length) i = message_save_max_length - 1; + while(i-- > 0){ + if((*sp & SCREEN_M_DATA) == '\n') break; + *message_save_buff++ = *sp++ & SCREEN_M_DATA; + } + *message_save_buff = '\0'; + +}/* End Routine */ + + + +/* SCREEN_LABEL_LINE - Routine to change the contents of the label line + * + * Function: + * + * This routine is called to change the label line + */ +int +screen_label_line(buffer,string,field) +struct buff_header *buffer; +char *string; +int field; +{ +register short *sp; +register char *cp; +register int i; +register int column; +register struct window *wptr; + + PREAMBLE(); + +/* + * If the field specified is out of range, punt + */ + if(field < 0 || field >= SCREEN_MAX_LABEL_FIELDS){ + screen_message("Illegal field specified to label line\n"); + return(FAIL); + }/* End IF */ + +/* + * We want to modify the label in any window which is displaying this buffer. + * There could be several windows with the same buffer displayed... + */ + for(wptr = window_list ; wptr != NULL; wptr = wptr->win_next_window){ + if(wptr->win_buffer != buffer) continue; +/* + * If no space has been allocated for this field yet, we need to call the + * allocator. Notice the overkill in that each field gets more than enough + * memory. + */ + if(wptr->win_label_field_contents[field] == NULL){ + wptr->win_label_field_contents[field] = + tec_alloc(TYPE_C_LABELFIELD,SCREEN_NOMINAL_LINE_WIDTH); + if(wptr->win_label_field_contents[field] == NULL){ + return(FAIL); + }/* End IF */ + *(wptr->win_label_field_contents[field]) = '\0'; + }/* End IF */ +/* + * If the current contents of the field are already what we want, we don't + * have to change a thing. + */ + if(strcmp(string,wptr->win_label_field_contents[field]) == 0) continue; + (void) strcpy(wptr->win_label_field_contents[field],string); +/* + * Figure out which column the field begins on. This changes as fields change, + * so it must be computed each time a change happens. + */ + for(i = 0 , column = 0; i < field; i++){ + if(wptr->win_label_field_contents[i] == NULL) continue; + column += strlen(wptr->win_label_field_contents[i]) + 1; + }/* End FOR */ +/* + * Now we have to update the label line from this column onward until we + * reach the end of the final field. + */ + sp = &wptr->win_label_line.fmt_buffer[column]; + for(i = field; i < SCREEN_MAX_LABEL_FIELDS; i++){ + cp = wptr->win_label_field_contents[i]; + if(cp == NULL) continue; + while(*cp){ + if(column >= term_columns) break; + *sp++ = *cp++ | SCREEN_M_REVERSE; + column += 1; + }/* End While */ + if(column >= term_columns) break; + *sp++ = ' ' | SCREEN_M_REVERSE; + column += 1; + }/* End FOR */ +/* + * Blank fill to the end of the screen incase we shrank the label line. + */ + while(column < term_columns){ +#ifdef TERMCAP + *sp++ = termcap_sg ? '-' : ' ' | SCREEN_M_REVERSE; +#endif +#ifdef TERMINFO + *sp++ = (terminfo_magic_cookie_glitch > 0) ? + '-' : ' ' | SCREEN_M_REVERSE; +#endif + column += 1; + }/* End While */ +/* + * Insure that the next screen refresh knows this line has changed + */ + wptr->win_label_line.fmt_sequence = screen_sequence; + + }/* End FOR */ + + return( SUCCESS ); + +}/* End Routine */ + + + +/* SCREEN_LABEL_WINDOW - Routine to set the window number field + * + * Function: + * + * This routine is called to modify the TECONAME field of the + * label lines to reflect the window number. + */ +int +screen_label_window() +{ +register short *sp; +register char *cp; +register int i; +register int column; +register struct window *wptr; +char tmp_buffer[SCREEN_NOMINAL_LINE_WIDTH]; +int field = LABEL_C_TECONAME; + + PREAMBLE(); + +/* + * We want to check the label in every window + */ + for(wptr = window_list ; wptr != NULL; wptr = wptr->win_next_window){ +/* + * If no space has been allocated for this field yet, we need to call the + * allocator. Notice the overkill in that each field gets more than enough + * memory. + */ + if(wptr->win_label_field_contents[field] == NULL){ + wptr->win_label_field_contents[field] = + tec_alloc(TYPE_C_LABELFIELD,SCREEN_NOMINAL_LINE_WIDTH); + if(wptr->win_label_field_contents[field] == NULL){ + return(FAIL); + }/* End IF */ + *(wptr->win_label_field_contents[field]) = '\0'; + }/* End IF */ +/* + * If the current contents of the field are already what we want, we don't + * have to change a thing. + */ + sprintf(tmp_buffer," TECO-%d",wptr->win_window_number); + if(strcmp(tmp_buffer,wptr->win_label_field_contents[field]) == 0){ + continue; + }/* End IF */ + + (void) strcpy(wptr->win_label_field_contents[field],tmp_buffer); + +/* + * Now we have to update the label line from this column onward until we + * reach the end of the final field. + */ + column = 0; + sp = &wptr->win_label_line.fmt_buffer[column]; + for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ + cp = wptr->win_label_field_contents[i]; + if(cp == NULL) continue; + while(*cp){ + if(column >= term_columns) break; + *sp++ = *cp++ | SCREEN_M_REVERSE; + column += 1; + }/* End While */ + if(column >= term_columns) break; + *sp++ = ' ' | SCREEN_M_REVERSE; + column += 1; + }/* End FOR */ +/* + * Blank fill to the end of the screen incase we shrank the label line. + */ + while(column < term_columns){ + *sp++ = ' ' | SCREEN_M_REVERSE; + column += 1; + }/* End While */ +/* + * Insure that the next screen refresh knows this line has changed + */ + wptr->win_label_line.fmt_sequence = screen_sequence; + + }/* End FOR */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* SCREEN_FORMAT_WINDOWS - Generate display contents for all windows + * + * Function: + * + * This is the main entry point to the screen formatting function. + * It causes screen format_lines to be generated for all the buffers + * that are currently visible on the screen. + */ +void +screen_format_windows() +{ +register struct window *wptr; + + PREAMBLE(); + + for(wptr = window_list; wptr != NULL; wptr = wptr->win_next_window){ + screen_display_window(wptr); + }/* End FOR */ + +}/* End Routine */ + +/* SCREEN_REFORMAT_WINDOWS - Reformat windows (because tab size changed) + * + * Function: + * + * This entry point is called when the format buffers of all the + * displayed windows need to be regenerated. The current case in + * mind is when the user changes the tab stops - this causes all + * the format lines to be obsolete. + */ +void +screen_reformat_windows() +{ +register int i; +register struct screen_line *lp; + + PREAMBLE(); + +/* + * The screen_gc_format_line is going to scavenge all the lines because + * we're making them look not-used here. However, we still go ahead and + * clobber the line_buffer's pointer, because we don't like the idea of + * having hanging pointers pointed at our screen lines. + */ + if(saved_screen_valid){ + for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ + if(lp->companion){ + if(lp->companion->fmt_saved_line != lp){ + fprintf( + stderr, + "lp->companion 0x%x != ->fmt_saved_line 0x%x\n", + (unsigned int)lp->companion, + (unsigned int)lp->companion->fmt_saved_line + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + if( lp->companion->fmt_permanent ) continue; + lp->companion->fmt_saved_line = NULL; + lp->companion = NULL; + }/* End IF */ + }/* End FOR */ + }/* End IF */ + + screen_gc_format_lines(); + +}/* End Routine */ + +/* SCREEN_DISPLAY_WINDOW - Generate display for a window + * + * Function: + * + * This routine is called to build up the format line descriptor + * structures which represent the edit buffer displayed in the + * specified window. + */ +int +screen_display_window(wptr) +register struct window *wptr; +{ +struct buff_header *hbp; +struct buff_line *tlbp,*blbp; +struct format_line *tsbp,*bsbp; +struct screen_line *sp; +register int i; +int lines; +int anchor_line; +char saw_dot; + + PREAMBLE(); + +/* + * Find a line in our saved screen array that still maps to a buffer location + */ + sp = saved_screen + wptr->win_y_base; + anchor_line = -1; + for(i = wptr->win_y_base; i < wptr->win_y_end; i++,sp++){ + if(sp->companion == NULL) continue; + if(sp->companion->fmt_owning_buffer != wptr->win_buffer) continue; + anchor_line = i; + break; + }/* End FOR */ + + if(anchor_line < 0){ + return(screen_rebuild_from_scratch(wptr)); + }/* End IF */ + +/* + * Now walk the top back + */ + tsbp = sp->companion; + tlbp = tsbp->fmt_buffer_line; + while(i > wptr->win_y_base){ + +/* + * If there is a format line in front of the one we are on, just back + * up to it. + */ + if(tsbp->fmt_prev_line){ + tsbp = tsbp->fmt_prev_line; + i -= 1; + continue; + }/* End IF */ +/* + * Else if there is a buffer line in front of us, get to the final + * format line associated with it. + */ + if(tlbp->prev_line != NULL){ + tlbp = tlbp->prev_line; + tsbp = screen_find_window_format_line(wptr,tlbp); + if(tsbp == NULL){ + if(screen_format_buff_line(wptr,tlbp) == FAIL) return(FAIL); + tsbp = screen_find_window_format_line(wptr,tlbp); + }/* End IF */ + while(tsbp->fmt_next_line) tsbp = tsbp->fmt_next_line; + i -= 1; + continue; + }/* End IF */ +/* + * If we got here, that means that before we could work back up to the old + * top line, we hit the begining of the buffer. Give up. + */ + return(screen_rebuild_from_scratch(wptr)); + + }/* End While */ + +/* + * If we make it to here, we have worked back to the top of the screen, now + * we want to work down to the bottom of the screen. + */ + lines = 1; + blbp = tlbp; + bsbp = tsbp; + +/* + * Determine the format_line that dot lies on so that as we work forward we can + * keep an eye out for it. + */ + hbp = wptr->win_buffer; + buff_find_line(hbp,hbp->dot); + screen_find_dot(wptr); + saw_dot = 0; + + while(lines <= wptr->win_y_size){ + +/* + * If we see the line that dot is on, remember it. The one case when we don't + * do this is if it is the top line of the screen. The reason for this is to + * avoid over-optimizing such that the user makes changes but never sees them + * because the changes are above the top line of the screen. + */ + if(bsbp == wptr->win_dot_format_line && lines != 1) saw_dot = 1; + +/* + * If there is a format line below our current bottom one, simply go + * forward to it. + */ + if(bsbp->fmt_next_line){ + bsbp = bsbp->fmt_next_line; + lines += 1; + continue; + }/* End IF */ +/* + * If we have to go on to the next buffer line, but there is not one there, + * that means that we hit the end of the buffer. + */ + if(blbp->next_line == NULL){ + return(screen_rebuild_from_scratch(wptr)); + }/* End IF */ +/* + * Else, if there is another buffer line after this, go to the first + * format line associated with it. + */ + if(blbp->next_line != NULL){ + blbp = blbp->next_line; + bsbp = screen_find_window_format_line(wptr,blbp); + if(bsbp == NULL){ + if(screen_format_buff_line(wptr,blbp) == FAIL) return(FAIL); + bsbp = screen_find_window_format_line(wptr,blbp); + }/* End IF */ + lines += 1; + }/* End IF */ + + }/* End While */ + +/* + * Test to see whether we ever saw dot. If not, we need to do the scratch + * screen rebuild. + */ + if(curwin == wptr && saw_dot == 0){ + return(screen_rebuild_from_scratch(wptr)); + }/* End IF */ + +/* + * Now we can start setting up the screen appearance + */ + sp = saved_screen + wptr->win_y_base; + + for(i = wptr->win_y_base; i < wptr->win_y_end; i++,sp++){ + if(tlbp == NULL){ + if(sp->companion) sp->companion->fmt_saved_line = NULL; + sp->companion = NULL; + continue; + }/* End IF */ + + if(sp->companion != tsbp){ + if(sp->companion) sp->companion->fmt_saved_line = NULL; + sp->companion = NULL; + if(tsbp->fmt_saved_line) tsbp->fmt_saved_line->companion = NULL; + tsbp->fmt_saved_line = NULL; + sp->companion = tsbp; + tsbp->fmt_saved_line = sp; + tsbp->fmt_sequence = screen_sequence; + }/* End IF */ + + if(curwin == wptr && tsbp == wptr->win_dot_format_line){ + cursor_y = i; + cursor_x = wptr->win_dot_screen_offset; + }/* End IF */ + + tsbp = tsbp->fmt_next_line; + if(tsbp == NULL){ + tlbp = tlbp->next_line; + if(tlbp){ + tsbp = screen_find_window_format_line(wptr,tlbp); + }/* End IF */ + }/* End IF */ + }/* End FOR */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* SCREEN_REBUILD_FROM_SCRATCH - Things have changed a lot, just rebuild it + * + * Function: + * + * This routine is called to build up the format line descriptor + * structures which represent the current edit buffer. + */ +int +screen_rebuild_from_scratch(wptr) +register struct window *wptr; +{ +struct buff_header *hbp; +struct buff_line *tlbp,*blbp; +struct format_line *tsbp,*bsbp; +struct screen_line *scrbp; +register int i; +int lines,olines; + + PREAMBLE(); + +/* + * Find the buffer line that dot is on, and then the format_line that + * it is on. + */ + hbp = wptr->win_buffer; + tlbp = blbp = buff_find_line(hbp,hbp->dot); + if(screen_find_dot(wptr) == FAIL) return(FAIL); + tsbp = bsbp = wptr->win_dot_format_line; + + lines = 1; + +/* + * Now walk the top back and the bottom forward + */ + while(lines < wptr->win_y_size){ + olines = lines; + +/* + * If there is a format_line in front of the one we are on, just back + * up to it. + */ + if(tsbp->fmt_prev_line){ + tsbp = tsbp->fmt_prev_line; + lines += 1; + }/* End IF */ + +/* + * Else if there is a buffer line in front of us, get to the final + * format_line associated with it. + */ + else if(tlbp->prev_line != NULL){ + tlbp = tlbp->prev_line; + tsbp = screen_find_window_format_line(wptr,tlbp); + if(tsbp == NULL){ + if(screen_format_buff_line(wptr,tlbp) == FAIL) return(FAIL); + tsbp = screen_find_window_format_line(wptr,tlbp); + }/* End IF */ + while(tsbp && tsbp->fmt_next_line) tsbp = tsbp->fmt_next_line; + lines += 1; + }/* End IF */ + +/* + * If there is a format_line below our current bottom one, simply go + * forward to it. + */ + if(bsbp->fmt_next_line){ + bsbp = bsbp->fmt_next_line; + lines += 1; + }/* End IF */ + +/* + * Else, if there is another buffer line after this, go to the first + * format_line associated with it. + */ + else if(blbp->next_line != NULL){ + blbp = blbp->next_line; + bsbp = screen_find_window_format_line(wptr,blbp); + if(bsbp == NULL){ + if(screen_format_buff_line(wptr,blbp) == FAIL) return(FAIL); + bsbp = screen_find_window_format_line(wptr,blbp); + }/* End IF */ + lines += 1; + }/* End IF */ + +/* + * If we havn't made any progress, then the buffer is smaller than the + * screen, so just display it. + */ + if(lines == olines) break; + + }/* End While */ + +/* + * Now we can start setting up the screen appearance + */ + scrbp = saved_screen + wptr->win_y_base; + + for(i = wptr->win_y_base; i < wptr->win_y_end; i++,scrbp++){ + if(tlbp == NULL){ + if(scrbp->companion) scrbp->companion->fmt_saved_line = NULL; + scrbp->companion = NULL; + continue; + }/* End IF */ + + if(scrbp->companion != tsbp){ + if(scrbp->companion) scrbp->companion->fmt_saved_line = NULL; + scrbp->companion = NULL; + if(tsbp->fmt_saved_line){ + tsbp->fmt_saved_line->companion = NULL; + tsbp->fmt_saved_line = NULL; + }/* End IF */ + scrbp->companion = tsbp; + tsbp->fmt_saved_line = scrbp; + tsbp->fmt_sequence = screen_sequence; + }/* End IF */ + + if(curwin == wptr && tsbp == wptr->win_dot_format_line){ + cursor_y = i; + cursor_x = wptr->win_dot_screen_offset; + }/* End IF */ + + tsbp = tsbp->fmt_next_line; + if(tsbp == NULL){ + tlbp = tlbp->next_line; + if(tlbp){ + tsbp = screen_find_window_format_line(wptr,tlbp); + if(tsbp == NULL){ + if(screen_format_buff_line(wptr,tlbp) == FAIL){ + return(FAIL); + }/* End IF */ + tsbp = screen_find_window_format_line(wptr,tlbp); + }/* End IF */ + }/* End IF */ + }/* End IF */ + }/* End FOR */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* SCREEN_SCROLL - Move the current screen up or down specified # lines + * + * Function: + * + * Here in response to the ES command. The user can scroll the screen + * up and down using this command. The current edit position does not + * move, so it is pointless to move it such that 'dot' moves off of the + * screen. + */ +void +screen_scroll(count) +int count; +{ +struct window *wptr = curwin; +struct screen_line *sp; +register int i; + + PREAMBLE(); + + i = count; + if(i < 0) i = -i; + if(i > wptr->win_y_end) return; + +/* + * If the scroll is forward, move the saved lines up in the array by count + */ + if(count > 0){ + sp = &saved_screen[wptr->win_y_base]; + for(i = wptr->win_y_base; i < wptr->win_y_end - count; i++,sp++){ + if(sp->companion) sp->companion->fmt_saved_line = NULL; + sp->companion = (sp+count)->companion; + (sp+count)->companion = NULL; + if(sp->companion == NULL) continue; + sp->companion->fmt_saved_line = sp; + sp->companion->fmt_sequence = screen_sequence; + }/* End FOR */ + sp = &saved_screen[wptr->win_y_end - count]; + for(i = wptr->win_y_end - count; i < wptr->win_y_end; i++,sp++){ + if(sp->companion == NULL) continue; + sp->companion->fmt_saved_line = NULL; + sp->companion = NULL; + }/* End FOR */ + }/* End IF */ +/* + * Else, if the scroll is backward, move the saved lines down in the array + */ + else if(count < 0){ + sp = &saved_screen[wptr->win_y_end - 1]; + for(i = wptr->win_y_end - 1; i >= wptr->win_y_base - count; i--,sp--){ + if(sp->companion) sp->companion->fmt_saved_line = NULL; + sp->companion = (sp+count)->companion; + (sp+count)->companion = NULL; + if(sp->companion == NULL) continue; + sp->companion->fmt_saved_line = sp; + sp->companion->fmt_sequence = screen_sequence; + }/* End FOR */ + sp = &saved_screen[wptr->win_y_base]; + for(i = wptr->win_y_base; i < wptr->win_y_base - count; i++,sp++){ + if(sp->companion == NULL) continue; + sp->companion->fmt_saved_line = NULL; + sp->companion = NULL; + }/* End FOR */ + }/* End IF */ + +}/* End Routine */ + + + +/* SCREEN_GC_FORMAT_LINES - Garbage collect unused format line structures + * + * Function: + * + * This routine determines which format structures are in use and + * deallocates the rest of them so that we don't use an excessive + * number of them. + */ +void +screen_gc_format_lines() +{ +register struct format_line *sbp,*tsbp; +register struct screen_line *sp; +register struct buff_line *lbp; +register int i; +struct window *wptr; + + PREAMBLE(); + +/* + * First mark all the format_line structures as unused + */ + sbp = format_line_alloc_list; + while(sbp){ + sbp->fmt_in_use = 0; + sbp = sbp->fmt_next_alloc; + }/* End While */ + +/* + * Now for each displayed line, find the format structure associated with it. + * Then find the line_buffer structure which owns that format structure and + * mark all of the format structures under it as in use. + */ + for(wptr = window_list ; wptr != NULL; wptr = wptr->win_next_window){ + sp = saved_screen + wptr->win_y_base; + for(i = wptr->win_y_base; i < wptr->win_y_end; i++,sp++){ + sbp = sp->companion; + if(sbp == NULL) continue; + if(sbp->fmt_permanent) continue; + if(sbp->fmt_buffer_line == NULL) continue; + sbp = + screen_find_window_format_line( + wptr, + sbp->fmt_buffer_line + ); + if(sbp == NULL) continue; + while(sbp){ + sbp->fmt_in_use = 1; + sbp = sbp->fmt_next_line; + }/* End While */ + }/* End FOR */ + }/* End FOR */ +/* + * Now go back and deallocate any format_line structures which are not in use + */ + sbp = format_line_alloc_list; + tsbp = NULL; + while(sbp){ + if(sbp->fmt_in_use){ + tsbp = sbp; + sbp = sbp->fmt_next_alloc; + continue; + }/* End IF */ + + lbp = sbp->fmt_buffer_line; + + screen_free_window_format_lines(sbp->fmt_window_ptr,lbp); + + sbp = tsbp ? tsbp : format_line_alloc_list; + + }/* End While */ + +}/* End Routine */ + + + +/* SCREEN_FREE_WINDOW_FORMAT_LINES - Deallocate a window's format lines + * + * Function: + * + * This routine is called to deallocate the list of format lines + * linked off of a line buffer, if they are associated with the + * specified window. + */ +void +screen_free_window_format_lines(wptr,lbp) +struct window *wptr; +register struct buff_line *lbp; +{ +register struct format_line *sbp,*tsbp; + + PREAMBLE(); + +/* + * There are a couple cases to check for: The first situation is when + * the very first format_line (chained off the buff_line structure) is + * the one we want to clobber. + */ + sbp = lbp->format_line; + if(sbp == NULL) return; + + if(sbp->fmt_window_ptr == wptr){ + format_lines_invalid = 1; + tsbp = sbp->fmt_next_window; + lbp->format_line = tsbp; + if(tsbp) tsbp->fmt_prev_window = NULL; + sbp->fmt_next_window = NULL; + screen_free_format_lines(sbp); + format_lines_invalid = 0; + return; + }/* End IF */ + +/* + * Here if it's not the head of the list. We want to keep the forward + * and backward pointers consistent, so we need to treat this case a + * little differently. + */ + while((tsbp = sbp->fmt_next_window)){ + if(tsbp->fmt_window_ptr == wptr){ + format_lines_invalid = 1; + sbp->fmt_next_window = tsbp->fmt_next_window; + if(tsbp->fmt_next_window){ + tsbp->fmt_next_window->fmt_prev_window = sbp; + }/* End IF */ + + tsbp->fmt_next_window = tsbp->fmt_prev_window = NULL; + screen_free_format_lines(tsbp); + format_lines_invalid = 0; + return; + }/* End IF */ + + sbp = tsbp; + + }/* End While */ + +}/* End Routine */ + + + +/* SCREEN_FREE_FORMAT_LINES - Deallocate a list of format lines + * + * Function: + * + * This routine will clean up all the storage associated with a list of + * format lines. It is called with the address of the head of the list, + * and it will continue on down until the entire list is cleaned up. + * It will also take care of cleaning up any companion pointers from the + * screen array. + */ +void +screen_free_format_lines(sbp) +register struct format_line *sbp; +{ +register struct format_line *osbp; +register struct format_line *next_win; + PREAMBLE(); + +/* + * Loop on down the list till we hit the tail + */ + while(sbp){ + +/* + * If this structure is tied to one of the current screen lines, break the + * connection. + */ + if(sbp->fmt_saved_line){ + if(sbp->fmt_saved_line->companion != sbp){ + fprintf( + stderr, + "sbp->fmt_saved_line 0x%x != sbp->...companion 0x%x\n", + (unsigned int)sbp->fmt_saved_line, + (unsigned int)sbp->fmt_saved_line->companion + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + sbp->fmt_saved_line->companion = NULL; + sbp->fmt_saved_line = NULL; + }/* End IF */ + + next_win = sbp->fmt_next_window; + if(next_win){ + sbp->fmt_next_window = NULL; + sbp->fmt_prev_window = NULL; + if(sbp->fmt_next_line){ + sbp->fmt_next_line->fmt_next_window = next_win; + }/* End IF */ + }/* End IF */ +/* + * Take it off the head of the list and place it on the free list. + */ + sbp->fmt_magic = MAGIC_FORMAT_LOOKASIDE; + osbp = sbp; + sbp = sbp->fmt_next_line; + if(sbp == NULL) sbp = next_win; + osbp->fmt_next_line = format_line_free_list; + format_line_free_list = osbp; + +/* + * Remove it from the allocated list. This list is used to find all + * the format_line structures which are outstanding at any given time. + */ + if(osbp->fmt_prev_alloc){ + osbp->fmt_prev_alloc->fmt_next_alloc = osbp->fmt_next_alloc; + }/* End IF */ + + else if(format_line_alloc_list == osbp){ + format_line_alloc_list = osbp->fmt_next_alloc; + }/* End Else */ + + if(osbp->fmt_next_alloc){ + osbp->fmt_next_alloc->fmt_prev_alloc = osbp->fmt_prev_alloc; + }/* End IF */ + + osbp->fmt_next_alloc = osbp->fmt_prev_alloc = NULL; + + }/* End While */ + +}/* End Routine */ + +void +screen_check_format_lines(sbp,who) +register struct format_line *sbp; +int who; +{ + + PREAMBLE(); + +/* + * Loop on down the list till we hit the tail + */ + while(sbp){ + +/* + * If this structure is tied to one of the current screen lines, break the + * connection. + */ + if(sbp->fmt_saved_line){ + if(sbp->fmt_saved_line->companion != sbp){ + fprintf( + stderr, + "w%d sbp->fmt_saved_line 0x%x != sbp->...companion 0x%x\n", + who, + (unsigned int)sbp->fmt_saved_line, + (unsigned int)sbp->fmt_saved_line->companion + ); + CAUSE_BUS_ERROR(); + }/* End IF */ + }/* End IF */ + +/* + * Take it off the head of the list and place it on the free list. + */ + sbp = sbp->fmt_next_line; + + }/* End While */ + +} + + +/* SCREEN_DEALLOCATE_FORMAT_LOOKASIDE_LIST + * + * Function: + * + * This routine frees up any format_lines we've cached on our local + * lookaside list. + */ +void +screen_deallocate_format_lookaside_list() +{ +register struct format_line *sbp; + + PREAMBLE(); + + while((sbp = format_line_free_list) != NULL){ + format_line_free_list = sbp->fmt_next_line; + sbp->fmt_magic = 0; + tec_release(TYPE_C_SCREENBUF,(char *)sbp->fmt_buffer); + tec_release(TYPE_C_SCREEN,(char *)sbp); + }/* End While */ + +}/* End Routine */ + + + +/* SCREEN_FIND_WINDOW_FORMAT_LINE - Finds format lines for a particular window + * + * Function: + * + * Each buffer line has chained off of it the format lines which hold + * the data as it appears on the screen. In order to handle multiple + * windows, we need the ability to have a format line per visible + * window. Thus, from the buff_line structure, we chain off a list + * of format lines. Since a buffer line may need many format lines to + * represent it (because it may wrap) we need a list. In addition, + * we want to have one of these lists on a per-window basis, so we + * have a pointer to the next list of format lines for another window. + */ +struct format_line * +screen_find_window_format_line(wptr,lbp) +struct window *wptr; +struct buff_line *lbp; +{ +struct format_line *sbp; + + PREAMBLE(); + + sbp = lbp->format_line; + while(sbp){ + if(sbp->fmt_window_ptr == wptr) return(sbp); + sbp = sbp->fmt_next_window; + }/* End While */ + + return(NULL); + +}/* End Routine */ + + + +/* SCREEN_FORMAT_BUFF_LINE - Create the format_line structures for a buff line + * + * Function: + * + * This routine is called with the address of a buff_line structure. It + * creates the format_line structures which represent the printed version + * of the data, i.e., ready to be written to the terminal. + */ +int +screen_format_buff_line(wptr,lbp) +struct window *wptr; +struct buff_line *lbp; +{ +register struct format_line *sbp; +register char *cp; +register short *sp; +register unsigned int c; +register int i; +int byte_count; +int current_column; +char *multi_byte_echo; +char expand_buffer[MAXOF(32,MAX_TAB_WIDTH+1)]; + + PREAMBLE(); + +/* + * If we're called with a null lbp, we've run out of memory or something, + * and we just punt + */ + if(lbp == NULL) return(FAIL); + +/* + * If there is an old format structure hanging around (doubtful), make it + * go away. Note that this has been modified to try to deal with the new + * scheme where we have a different set of format structures for each + * window. + */ + if(lbp->format_line){ + screen_free_window_format_lines(wptr,lbp); + }/* End IF */ + + sbp = allocate_format_buffer(wptr,lbp); + if(sbp == NULL) return(FAIL); + + sbp->fmt_next_window = lbp->format_line; + if(sbp->fmt_next_window){ + sbp->fmt_next_window->fmt_prev_window = sbp; + }/* End IF */ + lbp->format_line = sbp; + + sp = sbp->fmt_buffer; + cp = lbp->buffer; + byte_count = lbp->byte_count; + c = ' '; + current_column = 0; + multi_byte_echo = NULL; + + while(byte_count > 0){ + + if(current_column >= term_columns){ + sbp->fmt_next_line = allocate_format_buffer(wptr,lbp); + if(sbp->fmt_next_line == NULL) return(FAIL); + sbp->fmt_next_line->fmt_prev_line = sbp; + sbp = sbp->fmt_next_line; + sp = sbp->fmt_buffer; + current_column = 0; + }/* End IF */ + + if(multi_byte_echo){ + c = *multi_byte_echo++; + if(*multi_byte_echo == '\0') multi_byte_echo = NULL; + }/* End IF */ + + else { + c = *cp++; + byte_count -= 1; + }/* End Else */ + + c &= 0xFF; + + switch(c){ + case '\t': + i = tab_width - ( current_column % tab_width ); + multi_byte_echo = tab_expand + MAX_TAB_WIDTH - i; + break; + case ESCAPE: + multi_byte_echo = "$"; + break; + + case '\n': + *sp++ = c; + current_column = 0; + break; + default: + if(c < ' '){ + if( hide_cr_flag ){ + expand_buffer[0] = ' '; + expand_buffer[1] = '\0'; + } else { + expand_buffer[0] = '^'; + expand_buffer[1] = c + 'A' - 1; + expand_buffer[2] = '\0'; + } + multi_byte_echo = expand_buffer; + continue; + }/* End IF */ +/* + * If the high bit is on, display it as the code with that bit stripped off, + * or as '.' if it is a non-printable character. We display it reverse video + * if the terminal is not a brain damaged 'magic cookie' terminal. + */ + if(eight_bit_output_flag == NO && c >= 128){ + c -= 128; + if(isprint(c)) c |= SCREEN_M_REVERSE; + else c = '.' | SCREEN_M_REVERSE; +#ifdef TERMCAP + if(termcap_sg){ + c &= ~SCREEN_M_REVERSE; + }/* End IF */ +#endif + }/* End IF */ + *sp++ = c; + current_column += 1; + break; + }/* End Switch */ + + }/* End While */ + + if(c != '\n'){ + if(current_column < (term_columns - 1)){ + *sp++ = '\n'; + }/* End IF */ + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* SCREEN_FIND_DOT - Find position of dot in a format_line + * + * Function: + * + * There are several times that we need to find the format buffer that is + * associated with dot. This routine determines which format_line dot is + * on, and the offset onto that line (in bytes). + */ +int +screen_find_dot(wptr) +struct window *wptr; +{ +register struct buff_line *lbp; +register struct format_line *sbp; +register char *cp; +register unsigned char c; +int i; +int byte_count; +int current_column; +char *multi_byte_echo; +char expand_buffer[32]; +struct buff_header *hbp = wptr->win_buffer; + + PREAMBLE(); + +/* + * Find the line buffer and format buffer structures + */ + lbp = buff_find_line(hbp,hbp->dot); + + sbp = screen_find_window_format_line(wptr,lbp); + if(sbp == NULL){ + if(screen_format_buff_line(wptr,lbp) == FAIL) return(FAIL); + sbp = screen_find_window_format_line(wptr,lbp); + }/* End IF */ + + cp = lbp->buffer; + byte_count = buff_find_offset(hbp,lbp,hbp->dot); + current_column = 0; + + multi_byte_echo = NULL; + + while(byte_count > 0 || multi_byte_echo){ + + if(current_column >= term_columns){ + sbp = sbp->fmt_next_line; + current_column = 0; + }/* End IF */ + + if(multi_byte_echo){ + c = *multi_byte_echo++; + if(*multi_byte_echo == '\0') multi_byte_echo = NULL; + }/* End IF */ + + else { + c = *cp++; + byte_count -= 1; + }/* End Else */ + + switch(c){ + case '\t': + i = tab_width - ( current_column % tab_width ); + multi_byte_echo = tab_expand + MAX_TAB_WIDTH - i; + break; + case ESCAPE: + multi_byte_echo = "$"; + break; + case '\n': + current_column = 0; + break; + default: + if(c < ' '){ + expand_buffer[0] = '^'; + expand_buffer[1] = c + 'A' - 1; + expand_buffer[2] = '\0'; + multi_byte_echo = expand_buffer; + continue; + }/* End IF */ + current_column += 1; + break; + }/* End Switch */ + + }/* End While */ + + wptr->win_dot_format_line = sbp; + wptr->win_dot_screen_offset = current_column; + + return(SUCCESS); + +}/* End Routine */ + + + +/* ALLOCATE_FORMAT_BUFFER - Routine to return a format_line structure + * + * Function: + * + * This routine will return a format_line structure to the caller. It + * first looks on the free list, and if there is not one there, it will + * create one the hard way. + */ +struct format_line * +allocate_format_buffer(wptr,lbp) +struct window *wptr; +struct buff_line *lbp; +{ +register struct format_line *sbp; +struct buff_header *hbp = wptr->win_buffer; + + PREAMBLE(); + + sbp = format_line_free_list; + if(sbp != NULL){ + if(sbp->fmt_buffer_size == term_columns){ + format_line_free_list = sbp->fmt_next_line; + } + else { + screen_deallocate_format_lookaside_list(); + sbp = NULL; + } + }/* End IF */ + + if(sbp == NULL){ + sbp = (struct format_line *) + tec_alloc(TYPE_C_SCREEN,sizeof(struct format_line)); + if(sbp == NULL) return(NULL); + + sbp->fmt_buffer_size = term_columns; + sbp->fmt_buffer = (short *) + tec_alloc( + TYPE_C_SCREENBUF, + (int)(sizeof(short) * sbp->fmt_buffer_size) + ); + if(sbp->fmt_buffer == NULL){ + tec_release(TYPE_C_SCREEN,(char *)sbp); + return(NULL); + }/* End IF */ + }/* End Else */ + + sbp->fmt_magic = MAGIC_FORMAT; + sbp->fmt_owning_buffer = hbp; + sbp->fmt_buffer_line = lbp; + sbp->fmt_sequence = screen_sequence; + sbp->fmt_next_line = sbp->fmt_prev_line = NULL; + sbp->fmt_next_window = sbp->fmt_prev_window = NULL; + sbp->fmt_window_ptr = wptr; + sbp->fmt_saved_line = NULL; + sbp->fmt_visible_line_position = -1; + sbp->fmt_permanent = 0; + +/* + * Place this at the head of the allocated queue + */ + sbp->fmt_next_alloc = format_line_alloc_list; + sbp->fmt_prev_alloc = NULL; + if(sbp->fmt_next_alloc) sbp->fmt_next_alloc->fmt_prev_alloc = sbp; + format_line_alloc_list = sbp; + + return(sbp); + +}/* End Routine */ + + + +/* SCREEN_REDRAW - Cause a redraw of the screen + * + * Function: + * + * This routine is called when the screen may have become corrupted + * and needs to be refreshed. + */ +void +screen_redraw() +{ +register struct screen_line *lp; +register short *sp; +register int i,j; + + PREAMBLE(); + + term_goto(0,0); + term_clrtobot(); + + for(lp = saved_screen, i = 0; i < term_lines; i++,lp++){ + sp = lp->buffer; + for(j = 0; j < term_columns; j++,sp++) *sp = ' '; + lp->sequence = 0; + }/* End FOR */ + + screen_format_windows(); + screen_refresh(); + +}/* End Routine */ + + + +/* CREATE_WINDOW - Create a window data structure + * + * Function: + * + * This function is called to create a window data structure which + * then occupies some portion of the screen. The initial structure + * starts out owning all but the reserved lines, but then lines are + * stolen from it as it is split into other windows. + */ +struct window * +create_window(x_size,y_size) +int x_size; +int y_size; +{ +register struct window *wptr; +register int i; + + PREAMBLE(); + + wptr = (struct window *)tec_alloc(TYPE_C_WINDOW,sizeof(struct window)); + if(wptr == NULL) return(NULL); + + wptr->win_label_line.fmt_buffer = (short *) + tec_alloc(TYPE_C_SCRBUF,(int)(sizeof(short) * x_size)); + if(wptr->win_label_line.fmt_buffer == NULL){ + tec_release(TYPE_C_WINDOW,(char *)wptr); + return(NULL); + }/* End IF */ + + wptr->win_window_number = next_window_number++; + wptr->win_next_window = NULL; + wptr->win_buffer = NULL; +/* + * Initialize the xy size of the window. The y gets set to size-1 to leave + * room for the label line. + */ + wptr->win_x_size = x_size; + wptr->win_y_size = y_size - 1; + wptr->win_y_base = 0; + wptr->win_y_end = wptr->win_y_base + wptr->win_y_size; +/* + * Initialize the label line structure. Each window has one of these in + * reverse video showing which buffer is being displayed. + */ + wptr->win_label_line.fmt_next_line = + wptr->win_label_line.fmt_prev_line = + wptr->win_label_line.fmt_next_alloc = + wptr->win_label_line.fmt_prev_alloc = NULL; + + wptr->win_label_line.fmt_permanent = 1; + wptr->win_label_line.fmt_sequence = 0; + wptr->win_label_line.fmt_owning_buffer = NULL; + wptr->win_label_line.fmt_visible_line_position = -1; + + wptr->win_label_line.fmt_window_ptr = wptr; /* FIX */ + wptr->win_label_line.fmt_buffer_line = NULL; + wptr->win_label_line.fmt_buffer_size = x_size; + + for(i = 0; i < x_size; i++){ + wptr->win_label_line.fmt_buffer[i] = ' ' | SCREEN_M_REVERSE; + }/* End FOR */ + + wptr->win_label_line.fmt_sequence = screen_sequence; + + for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ + wptr->win_label_field_contents[i] = NULL; + }/* End FOR */ + + return(wptr); + +}/* End Routine */ + + + +/* SCREEN_SPLIT_WINDOW - Make two where there is only one + * + * Function: + * + * This routine is called when the user wants to split the current window + * into two. This allows him to display several buffers simultaneously. + */ +struct window * +screen_split_window(old_wptr,lines,buffer_number) +register struct window *old_wptr; +int lines; +int buffer_number; +{ +register int i; +register struct window *new_wptr; +register struct screen_line *lp; + + PREAMBLE(); + + if(old_wptr->win_y_size < SCREEN_MINIMUM_WINDOW_HEIGHT){ + error_message("Window is already as small as possible"); + return(NULL); + }/* End IF */ + + if(lines < SCREEN_MINIMUM_WINDOW_HEIGHT){ + lines = SCREEN_MINIMUM_WINDOW_HEIGHT; + }/* End If */ + + if((old_wptr->win_y_size - (lines + 1)) < SCREEN_MINIMUM_WINDOW_HEIGHT){ + error_message("Resulting windows would be too small"); + return(NULL); + }/* End IF */ + + new_wptr = create_window(old_wptr->win_x_size,old_wptr->win_y_size-lines); + new_wptr->win_next_window = window_list; + window_list = new_wptr; + new_wptr->win_buffer = old_wptr->win_buffer; +/* + * Take the lines of the new window away from the old window + */ + old_wptr->win_y_size -= new_wptr->win_y_size + 1; +/* + * Have the new window start above the old one + */ + new_wptr->win_y_base = old_wptr->win_y_base; + old_wptr->win_y_base += new_wptr->win_y_size + 1; +/* + * Recalculate the ends of each window + */ + old_wptr->win_y_end = old_wptr->win_y_base + old_wptr->win_y_size; + new_wptr->win_y_end = new_wptr->win_y_base + new_wptr->win_y_size; +/* + * Initialize the label line structure. Each window has one of these in + * reverse video showing which buffer is being displayed. + */ + for(i = 0; i < new_wptr->win_x_size; i++){ + new_wptr->win_label_line.fmt_buffer[i] = + old_wptr->win_label_line.fmt_buffer[i]; + }/* End FOR */ + + for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ + if(old_wptr->win_label_field_contents[i]){ + screen_label_line(old_wptr->win_buffer, + old_wptr->win_label_field_contents[i],i); + }/* End IF */ + }/* End FOR */ + + screen_label_window(); + + lp = saved_screen + new_wptr->win_y_end; + if(new_wptr->win_label_line.fmt_saved_line){ + new_wptr->win_label_line.fmt_saved_line->companion = NULL; + }/* End IF */ + new_wptr->win_label_line.fmt_saved_line = lp; + if(lp->companion) lp->companion->fmt_saved_line = NULL; + lp->companion = &new_wptr->win_label_line; + new_wptr->win_label_line.fmt_sequence = screen_sequence; + + if(buffer_number){ + buff_openbuffnum(buffer_number,0); + }/* End IF */ + + return(new_wptr); + +}/* End Routine */ + + + +/* SCREEN_DELETE_WINDOW - Here to delete a window + * + * Function: + * + * This function is called when a user wants to delete a window. + * The space gets given back to the window next to it. + */ +void +screen_delete_window(old_wptr) +register struct window *old_wptr; +{ + +register int i; +register struct window *new_wptr; +register struct screen_line *lp; + + PREAMBLE(); + + if((old_wptr->win_y_size + 1) == (term_lines - SCREEN_RESERVED_LINES)){ + error_message("Illegal to delete the final window"); + return; + }/* End IF */ +/* + * Find the old_wptr on the list of windows, and remove it + */ + if(window_list == old_wptr) window_list = old_wptr->win_next_window; + else { + new_wptr = window_list; + while(new_wptr->win_next_window != old_wptr){ + new_wptr = new_wptr->win_next_window; + }/* End While */ + new_wptr->win_next_window = old_wptr->win_next_window; + }/* End Else */ +/* + * Remove the label line from the screen display + */ + lp = old_wptr->win_label_line.fmt_saved_line; + lp->companion = NULL; + old_wptr->win_label_line.fmt_saved_line = NULL; +/* + * If the window is at the bottom of the screen, we will combine it with + * the window above it. + */ + if((old_wptr->win_y_end + 1) == (term_lines - SCREEN_RESERVED_LINES)){ + new_wptr = window_list; + while(new_wptr){ + if((new_wptr->win_y_end + 1) == old_wptr->win_y_base) break; + new_wptr = new_wptr->win_next_window; + }/* End While */ + if(!new_wptr){ + error_message("Failure to find window above"); + return; + }/* End IF */ + lp = new_wptr->win_label_line.fmt_saved_line; + lp->companion = NULL; + new_wptr->win_y_size += old_wptr->win_y_size + 1; + new_wptr->win_y_end = new_wptr->win_y_base + new_wptr->win_y_size; + lp = saved_screen + new_wptr->win_y_end; + lp->companion = &new_wptr->win_label_line; + new_wptr->win_label_line.fmt_saved_line = lp; + }/* End IF */ +/* + * Else, we combine it with the window below it + */ + else { + new_wptr = window_list; + while(new_wptr){ + if(new_wptr->win_y_base == (old_wptr->win_y_end + 1)) break; + new_wptr = new_wptr->win_next_window; + }/* End While */ + if(!new_wptr){ + error_message("Failure to find window below"); + return; + }/* End IF */ + lp = new_wptr->win_label_line.fmt_saved_line; + lp->companion = NULL; + new_wptr->win_y_base -= old_wptr->win_y_size + 1; + new_wptr->win_y_size += old_wptr->win_y_size + 1; + new_wptr->win_y_end = new_wptr->win_y_base + new_wptr->win_y_size; + lp = saved_screen + new_wptr->win_y_end; + lp->companion = &new_wptr->win_label_line; + new_wptr->win_label_line.fmt_saved_line = lp; + }/* End IF */ +/* + * Release any memory used to hold the strings in the label line + */ + for(i = 0; i < SCREEN_MAX_LABEL_FIELDS; i++){ + if(old_wptr->win_label_field_contents[i]){ + tec_release( + TYPE_C_LABELFIELD, + old_wptr->win_label_field_contents[i] + ); + old_wptr->win_label_field_contents[i] = NULL; + }/* End IF */ + }/* End FOR */ +/* + * Release the buffer used to hold formatted data + */ + tec_release(TYPE_C_SCRBUF,(char *)old_wptr->win_label_line.fmt_buffer); + tec_release(TYPE_C_WINDOW,(char *)old_wptr); + + curwin = new_wptr; + curbuf = new_wptr->win_buffer; + +}/* End Routine */ + + + +/* WINDOW_SWITCH - Switch to a new window + * + * Function: + * + * This function is called to switch the current window to a different + * window. + */ +int +window_switch(window_number) +int window_number; +{ +register struct window *wptr; + + PREAMBLE(); + +/* + * If the window number is specified as zero, just select the next window + * on the list. + */ + if(window_number == 0){ + if((wptr = curwin->win_next_window) == NULL) wptr = window_list; + window_number = wptr->win_window_number; + }/* End IF */ + + wptr = window_list; +/* + * Search the list until we find the correct window. + */ + while(wptr && wptr->win_window_number != window_number){ + wptr = wptr->win_next_window; + }/* End While */ +/* + * If the search failed, give up. + */ + if(wptr == NULL){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf(tmp_message,"can't switch to window %d",window_number); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + +/* + * Check to see that we actually found the right window. + */ + if(wptr->win_window_number != window_number) return(FAIL); +/* + * Switch the current window and edit buffer to the newly selected window. + */ + curwin = wptr; + return(SUCCESS); + +}/* End Routine */ diff --git a/tecexec.c b/tecexec.c new file mode 100644 index 0000000..6d94fd5 --- /dev/null +++ b/tecexec.c @@ -0,0 +1,2801 @@ +char *tecexec_c_version = "tecexec.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecexec.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecexec.c + * The SWITCH/CASE statements which implement execution stage of the parser + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + extern struct cmd_token *jump_to_token; + extern struct cmd_token *last_token_executed; + extern struct cmd_token *last_cmd_list; + extern struct search_buff search_string; + extern char user_message[PARSER_STRING_MAX]; + + extern char exit_flag; + extern struct window *window_list; + extern struct buff_header *curbuf,*buffer_headers; + extern struct buff_header *qregister_push_down_list; + extern int last_search_pos1,last_search_pos2; + extern int last_search_status; + extern int remembered_dot; + extern char intr_flag; + extern char immediate_execute_flag; + extern struct window *curwin; + + int find_conditional_else(struct cmd_token *); + int find_conditional_end(struct cmd_token *); + int compare_label(struct cmd_token *,struct cmd_token *); + void extract_label(struct cmd_token *,char *); + + + +/* EXECUTE_A_STATE - Do the runtime execution part of a command + * + * Function: + * + * This routine is called to execute the runtime part of a command token. + * This could be during immediate execution mode, directly after a token + * gets parsed, or it could be during final stages when we are either + * running commands which cannot be undone (like ex), or simply complex + * things like an iteration. The execute_state which we dispatch on was + * set by the parse state during syntax analysis. + */ +int +execute_a_state(ct,uct) +register struct cmd_token *ct; +struct cmd_token *uct; +{ +register struct undo_token *ut; +char tmp_buffer[LINE_BUFFER_SIZE],tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + + last_token_executed = ct; + + switch(ct->execute_state){ +/* + * Here to return the numeric value of a q-register. This has to be done + * at run time because the value of the q-register might be modified during + * the course of the command. Note that we return an error if the q-register + * has never been loaded. Otherwise, we return the value in tmpval so that + * it can be the input to further arithmetic expressions. + */ + case EXEC_C_QVALUE: + {/* Local Block */ + register struct buff_header *hbp; + + if(ct->q_register == '*'){ + ct->ctx.tmpval = curbuf->buffer_number; + return(SUCCESS); + }/* End IF */ + + hbp = buff_qfind(ct->q_register,0); + if(hbp == NULL){ + sprintf(tmp_message,"?Q-register %c empty",ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + ct->ctx.tmpval = hbp->ivalue; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to return the numeric value of a q-register and also increment it's + * value. Other than the incrementing, this is exactly like the QVALUE state. + */ + case EXEC_C_PERCENT_VALUE: + {/* Local Block */ + register struct buff_header *hbp; + + hbp = buff_qfind(ct->q_register,0); + if(hbp == NULL){ + sprintf(tmp_message,"?Q-register %c empty",ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Set up an undo block so that we can put the previous value back into + * the q-register if this command gets undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + + ut->opcode = UNDO_C_UQREGISTER; + ut->iarg1 = ct->q_register; + ut->iarg2 = hbp->ivalue; + + hbp->ivalue += ct->ctx.tmpval; + ct->ctx.tmpval = hbp->ivalue; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to read a file into a q-register. We also set up undo tokens to + * restore the previous contents of the q-register if he undoes this. + */ + case EXEC_C_EQQREGISTER: + {/* Local Block */ + register struct buff_header *qbp; + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = curbuf->buffer_number; + + qbp = buff_qfind(ct->ctx.tmpval,1); + + if(ct->ctx.tmpval == '*'){ + if(ct->ctx.carg){ + rename_edit_buffer(curbuf,ct->ctx.carg,uct); + }/* End IF */ + else { + load_qname_register(); + buff_switch(qbp,1); + }/* End Else */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + }/* End IF */ + return(SUCCESS); + }/* End IF */ + + if(ct->ctx.carg == NULL || ct->ctx.carg[0] == '\0'){ + buff_switch(qbp,1); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + }/* End IF */ + return(SUCCESS); + }/* End IF */ +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. Also, if the colon modifier was + * specified, then we append to the buffer instead of replacing it. + */ + if((ct->ctx.flags & CTOK_M_COLON_SEEN) == 0 && qbp->zee){ + buff_delete_with_undo(uct,qbp,0,qbp->zee); + }/* End IF */ + +/* + * Set up an undo argument to delete any characters which get inserted as + * part of the buff_read we do to load the q-register. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)qbp; + ut->iarg1 = qbp->dot; + ut->iarg2 = qbp->zee; + + np = expand_filename(ct->ctx.carg); + if(np == NULL){ + sprintf(tmp_message,"EQ No such file <%s>",ct->ctx.carg); + error_message(tmp_message); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + while(np){ + buff_read(qbp,np->we_name); + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + + ut->iarg2 = qbp->zee - ut->iarg2; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + }/* End IF */ + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to load a q-register with a numeric value. Note that not only do we + * have to put the new number into it, we also have to set up the undo token + * so that if this command is backed out of we can restore the previous value + * to the q-register. + */ + case EXEC_C_UQREGISTER: + {/* Local Block */ + register struct buff_header *hbp; +/* + * Find the q-register. If it does not exist yet, create it. + */ + hbp = buff_qfind(ct->q_register,1); + if(hbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Insure that we have an argument to store + */ + if(ct->ctx.iarg1_flag == NO){ + error_message("?Argument required for U command"); + return(FAIL); + }/* End IF */ +/* + * Set up an undo block so that we can put the previous value back into + * the q-register if this command gets undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_UQREGISTER; + ut->iarg1 = ct->q_register; + ut->iarg2 = hbp->ivalue; + + hbp->ivalue = ct->ctx.iarg1; + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to load the text contents of the specified q-register into the + * edit buffer at the current location. + */ + case EXEC_C_GQREGISTER: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_header *qbp; + + hbp = curbuf; +/* + * If this is the buffer name q-register, we have to do some special + * stuff. + */ + if(ct->q_register == '*') load_qname_register(); +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, declare an error. + */ + qbp = buff_qfind(ct->q_register,0); + if(qbp == NULL){ + sprintf(tmp_message,"?Q-register <%c> does not exist", + ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Test if the q-register is empty, and declare an error if it is + */ + if(qbp->zee == 0){ + sprintf(tmp_message,"?Q-register <%c> is empty", + ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Make sure the Q register and the edit buffer are not the same buffer + */ + if(qbp == hbp){ + strcpy(tmp_message, + "?Cannot use G to copy from a Q-register to itself"); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Now copy the text out of the q-register and insert it into the edit buffer + */ + buff_insert_from_buffer_with_undo(uct,hbp,hbp->dot,qbp,0,qbp->zee); + + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to execute the macro in the specified Q register + */ + case EXEC_C_MQREGISTER: + {/* Local Block */ + register struct buff_header *qbp; +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, declare an error. + */ + qbp = buff_qfind(ct->q_register,0); + if(qbp == NULL){ + sprintf(tmp_message,"?Q-register <%c> does not exist", + ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * It's an error if the q-register is empty + */ + if(qbp->zee == 0){ + sprintf(tmp_message,"?Q-register <%c> is empty", + ct->q_register); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * Set up the undo block, and pass its address to the tecmacro routine so + * that the tecmacro can chain the command list off of it. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_MACRO; + ut->carg1 = NULL; + + return(tecmacro(qbp,ct,(struct cmd_token **)&ut->carg1)); + + }/* End Local Block */ + +/* + * Here to load text into a q-register in response to the X command. + * This clears any existing text from the q-register, and then copies + * the specified text into it. + */ + case EXEC_C_XQREGISTER: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_header *qbp; + register struct buff_line *lbp; + register int i,j; + int pos1,pos2; + + hbp = curbuf; +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind(ct->q_register,1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Make sure the Q register and the edit buffer are not the same buffer + */ + if(qbp == hbp){ + strcpy(tmp_message, + "?Cannot use X to copy from a Q-register to itself"); + error_message(tmp_message); + return(FAIL); + }/* End IF */ +/* + * If two arguments were specified, then he has specified an a,b range to + * be copied. + */ + if(ct->ctx.iarg1_flag == YES && ct->ctx.iarg2_flag == YES){ + pos1 = ct->ctx.iarg1; + pos2 = ct->ctx.iarg2; + if(parse_illegal_buffer_position(pos1,pos2,"X")){ + return(FAIL); + }/* End IF */ + if(pos2 < pos1){ + pos2 = ct->ctx.iarg1; + pos1 = ct->ctx.iarg2; + }/* End IF */ + }/* End IF */ +/* + * Else, if it was a single command, it is a relative copy which affects a + * portion of the buffer in a similar way as if it was an L command. Our job + * now is to turn it into a a,b range. + */ + else { + lbp = buff_find_line(hbp,hbp->dot); + if(lbp == NULL) return(FAIL); +/* + * Start by pretending it was a 0L command, and get to the begining of + * the line. That allows us to not worry about offset onto current line. + */ + j = 0 - buff_find_offset(hbp,lbp,hbp->dot); + i = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) i = 1; +/* + * If argument is positive, movement is forward + */ + if(i > 0){ + while(i-- && lbp){ + j += lbp->byte_count; + lbp = lbp->next_line; + }/* End WHILE */ + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with X"); + return(FAIL); + }/* End IF */ + pos1 = hbp->dot; + pos2 = hbp->dot + j; + }/* End IF */ +/* + * Else, it is backwards + */ + else { + + while(i++ && lbp){ + lbp = lbp->prev_line; + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with X"); + return(FAIL); + }/* End IF */ + j -= lbp->byte_count; + }/* End WHILE */ + pos1 = hbp->dot + j; + pos2 = hbp->dot; + }/* End Else */ + }/* End Else */ +/* + * Now that we have calculated the buffer positions to be used by the command, + * we can actually do the work. + */ + j = pos2 - pos1; +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. Also, if the colon modifier was + * specified, then we append to the buffer instead of replacing it. + */ + if((ct->ctx.flags & CTOK_M_COLON_SEEN) == 0 && qbp->zee){ + buff_delete_with_undo(uct,qbp,0,qbp->zee); + }/* End IF */ +/* + * Now copy the new text from the edit buffer into the q-register + */ + buff_insert_from_buffer_with_undo(uct,qbp,qbp->zee,hbp,pos1,j); +/* + * If the q-register specified is '*', then the actual result is to rename + * the current buffer. + */ + if(ct->q_register == '*'){ + rename_edit_buffer(hbp,qbp->name,uct); + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to load text into a q-register in response to the * command. + * This clears any existing text from the q-register, and then copies + * the previous command string into it. + */ + case EXEC_C_SAVECOMMAND: + {/* Local Block */ + register struct buff_header *qbp; + register struct cmd_token *oct; + register int i; + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind(ct->q_register,1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. + */ + if(qbp->zee) buff_delete_with_undo(uct,qbp,0,qbp->zee); +/* + * Set up an undo token to delete the contents of the q-register that we + * are about to load. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)qbp; + ut->iarg1 = 0; + ut->iarg2 = 0; + +/* + * Now we go through the last command list and anytime we see an input + * byte, we copy it into the q-register. + */ + oct = last_cmd_list; + i = 0; + while(oct){ + if(oct->flags & TOK_M_EAT_TOKEN){ + buff_insert_char(qbp,i++,oct->input_byte); + ut->iarg2 += 1; + }/* End IF */ + oct = oct->next_token; + }/* End While */ + + return(SUCCESS); + }/* End Local Block */ +/* + * Here to push the specified q-register onto the q-register stack + */ + case EXEC_C_PUSH_QREGISTER: + if(ct->q_register == '*') load_qname_register(); + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + if(push_qregister(ct->q_register) != SUCCESS) return(FAIL); + ut->opcode = UNDO_C_POP; + return(SUCCESS); +/* + * Here to pop a Q register off of the stack and replace a Q register + * with it. Since this destroys the Q register we pop onto, we first + * must save the entire Q register. + */ + case EXEC_C_POP_QREGISTER: + {/* Local Block */ + register struct buff_header *pbp; + register struct buff_header *qbp; +/* + * Get a pointer to the pushed buffer structure. If there are none on + * the stack, then he has tried to pop too many, and this is an error. + */ + pbp = qregister_push_down_list; + + if(pbp == NULL){ + error_message("?Q-register push down list is empty"); + return(FAIL); + }/* End IF */ +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind(ct->q_register,1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Arrange to restore the numeric contents of the Q register if this + * should be undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_UQREGISTER; + ut->iarg1 = ct->q_register; + ut->iarg2 = qbp->ivalue; + + qbp->ivalue = pbp->ivalue; +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. + */ + if(qbp->zee){ + if(buff_delete_with_undo(uct,qbp,0,qbp->zee) == FAIL){ + return(FAIL); + }/* End IF */ + }/* End IF */ +/* + * Arrange for it to be deleted if this gets undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)qbp; + ut->iarg1 = 0; + ut->iarg2 = pbp->zee; +/* + * Transfer the contents of the pushed register into the actual Q register + */ + buff_bulk_insert(qbp,0,pbp->zee,pbp->first_line); + pbp->first_line = NULL; + qbp->pos_cache.lbp = NULL; + qbp->pos_cache.base = 0; + +/* + * Arrange that the push be re-done if the pop is undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_PUSH; + ut->iarg1 = ct->q_register; +/* + * Place it on the main buffer list so that buff_destroy can find it + */ + qregister_push_down_list = pbp->next_header; + pbp->next_header = buffer_headers; + buffer_headers = pbp; + buff_destroy(pbp); +/* + * If the q-register specified is '*', then the actual result is to rename + * the current buffer. + */ + if(ct->q_register == '*'){ + rename_edit_buffer(curbuf,qbp->name,uct); + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ + +/* + * Here to return the current position of 'dot' + */ + case EXEC_C_DOTVALUE: + ct->ctx.tmpval = curbuf->dot; + return(SUCCESS); +/* + * Here to return the number of bytes in the edit buffer + */ + case EXEC_C_ZEEVALUE: + ct->ctx.tmpval = curbuf->zee; + return(SUCCESS); +/* + * Here to return the value 0,Z which H is shorthand for + */ + case EXEC_C_HVALUE: + ct->ctx.iarg1 = 0; + ct->ctx.iarg2 = curbuf->zee; + return(SUCCESS); +/* + * Print out the numeric value of an expression + */ + case EXEC_C_EQUALS: + if(ct->ctx.iarg1_flag == NO){ + error_message("?Argument required for = command"); + return(FAIL); + }/* End IF */ + + sprintf(tmp_buffer,"value = %d",ct->ctx.iarg1); + screen_message(tmp_buffer); + return(SUCCESS); + + case EXEC_C_TWO_EQUALS: + sprintf(tmp_buffer,"value = 0%o",ct->ctx.iarg1); + screen_message(tmp_buffer); + return(SUCCESS); + + case EXEC_C_THREE_EQUALS: + sprintf(tmp_buffer,"value = 0x%X",ct->ctx.iarg1); + screen_message(tmp_buffer); + return(SUCCESS); +/* + * Here to insert the value of the expression into the edit buffer as a + * decimal representation. + */ + case EXEC_C_BACKSLASH: + {/* Local Block */ + register int i,j; + char *cp; + int length = 0; + + if(ct->ctx.iarg2 < 2 || ct->ctx.iarg2 > 36) ct->ctx.iarg2 = 10; + j = ct->ctx.iarg1; + cp = tmp_buffer + sizeof(tmp_buffer); + do { + i = j % ct->ctx.iarg2; + j = (j - i) / ct->ctx.iarg2; + if(i > 9) i += ('A' - 10); + else i += '0'; + *(--cp) = i; + length += 1; + } while(j); + + buff_insert_with_undo(uct,curbuf,curbuf->dot,cp,length); + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to interpret the ascii digits following 'dot' in the edit buffer + * as a decimal number, and return this number as the value of this expression, + * leaving 'dot' positioned after the digits. + */ + case EXEC_C_BACKSLASHARG: + {/* Local Block */ + register int i; + register int radix; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + radix = ct->ctx.tmpval; + ct->ctx.tmpval = 0; + + for(;;){ + i = buff_contents(curbuf,curbuf->dot); + + if(i >= '0' && i <= '9') i -= '0'; + else if(i >= 'a' && i <= 'z') i -= ('a' - 10); + else if(i >= 'A' && i <= 'Z') i -= ('A' - 10); + else break; + + if(i < 0 || i >= radix) break; + + ct->ctx.tmpval = ct->ctx.tmpval * radix + i; + if(curbuf->dot == curbuf->zee) break; + curbuf->dot++; + }/* End FOR */ + + return(SUCCESS); + + }/* End Local Block */ +/* + * The poor bastard wants to leave... + */ + case EXEC_C_EXITCOMMAND: + {/* Local Block */ + register struct buff_header *hbp; + + if(ct->ctx.iarg1_flag == NO || ct->ctx.iarg1 != -1){ + for(hbp = buffer_headers; hbp != NULL; hbp = hbp->next_header){ + if(hbp->buffer_number <= 0) continue; + if(hbp->ismodified != 0 && hbp->isreadonly == 0){ + error_message("?Modified buffers exist"); + return(FAIL); + }/* End IF */ + }/* End FOR */ + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SET_EXIT_FLAG; + + exit_flag = YES; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to jump to an absolute position in the buffer + */ + case EXEC_C_JUMP: + if(ct->ctx.iarg1_flag == YES && ct->ctx.iarg1 < 0){ + error_message("?Negative Argument to J"); + return(FAIL); + }/* End IF */ + + if(ct->ctx.iarg1_flag == NO) ct->ctx.iarg1 = 0; + + if(parse_illegal_buffer_position(ct->ctx.iarg1,0,"J")){ + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + if(ct->ctx.iarg1_flag == NO) curbuf->dot = 0; + else curbuf->dot = ct->ctx.iarg1; + return(SUCCESS); +/* + * Here to move dot by a relative number of buffer positions + */ + case EXEC_C_CHAR: + {/* Local Block */ + register int i; + + i = curbuf->dot + 1; + + if(ct->ctx.iarg1_flag == YES){ + i = curbuf->dot + ct->ctx.iarg1; + }/* End IF */ + + if(parse_illegal_buffer_position(i,0,"C")) return(FAIL); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + curbuf->dot = i; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to move dot by a relative number of buffer positions, just like + * the "C" command, except that the direction is by default backwards. + */ + case EXEC_C_RCHAR: + {/* Local Block */ + register int i; + + i = curbuf->dot - 1; + if(ct->ctx.iarg1_flag == YES){ + i = curbuf->dot - ct->ctx.iarg1; + }/* End IF */ + + if(parse_illegal_buffer_position(i,0,"R")) return(FAIL); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + curbuf->dot = i; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to delete the specified number of characters following 'dot'. + * A negative value indicates we should delete characters BEFORE 'dot'. + */ + case EXEC_C_DELETE: + {/* Local Block */ + register int i; + + i = 1; + if(ct->ctx.iarg1_flag == YES) i = ct->ctx.iarg1; + + if(parse_illegal_buffer_position(curbuf->dot+i,0,"D")){ + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + if(i < 0){ + i = 0 - i; + curbuf->dot -= i; + }/* End IF */ + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(i) buff_delete_with_undo(uct,curbuf,curbuf->dot,i); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to move 'dot' by the specified number of words. Note that words + * can have a pretty nebulous definition, depending on what you are doing. + * This should probably have a q-register which contains all of the delimeters. + */ + case EXEC_C_WORD: + {/* Local Block */ + register int count; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + count = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) count = 1; + + cmd_wordmove(count); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to delete the specified number of words. Note that words + * can have a pretty nebulous definition, depending on what you are doing. + * This should probably have a q-register which contains all of the delimeters. + */ + case EXEC_C_DELWORD: + {/* Local Block */ + register int count; + int original_position; + int pos; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = original_position = curbuf->dot; + + count = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) count = 1; + + cmd_wordmove(count); + + pos = original_position; + count = curbuf->dot - original_position; + if(count < 0){ + count = 0 - count; + pos = curbuf->dot; + }/* End IF */ + + if(count) buff_delete_with_undo(uct,curbuf,pos,count); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to delete backward the specified number of words. Note that + * words can have a pretty nebulous definition, depending on what you are + * doing. This should probably have a q-register which contains all of the + * delimeters. + */ + case EXEC_C_RDELWORD: + {/* Local Block */ + register int count; + int original_position; + int pos; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = original_position = curbuf->dot; + + count = 0 - ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) count = -1; + + cmd_wordmove(count); + + pos = original_position; + count = curbuf->dot - original_position; + if(count < 0){ + count = 0 - count; + pos = curbuf->dot; + }/* End IF */ + + if(count) buff_delete_with_undo(uct,curbuf,pos,count); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to move the 'dot' by the specified number of lines. + */ + case EXEC_C_LINE: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_line *lbp; + register int i,j; + + hbp = curbuf; + lbp = buff_find_line(hbp,hbp->dot); +/* + * Start by pretending it was a 0L command, and get to the begining of + * the line. That allows us to not worry about offset onto current line. + */ + j = 0 - buff_find_offset(hbp,lbp,hbp->dot); + i = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) i = 1; +/* + * If argument is positive, movement is forward + */ + if(i > 0){ + while(i-- && lbp){ + j += lbp->byte_count; + lbp = lbp->next_line; + }/* End WHILE */ + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with L"); + return(FAIL); + }/* End IF */ + }/* End IF */ + + else { + while(i++ && lbp){ + lbp = lbp->prev_line; + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with L"); + return(FAIL); + }/* End IF */ + j -= lbp->byte_count; + }/* End WHILE */ + }/* End Else */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = hbp->dot; + + hbp->dot += j; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to move the 'dot' backward by the specified number of lines. + */ + case EXEC_C_RLINE: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_line *lbp; + register int i,j; + + hbp = curbuf; + lbp = buff_find_line(hbp,hbp->dot); +/* + * Start by pretending it was a 0L command, and get to the begining of + * the line. That allows us to not worry about offset onto current line. + */ + j = 0 - buff_find_offset(hbp,lbp,hbp->dot); + i = 0 - ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) i = -1; +/* + * If argument is positive, movement is forward + */ + if(i > 0){ + while(i-- && lbp){ + j += lbp->byte_count; + lbp = lbp->next_line; + }/* End WHILE */ + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with B"); + return(FAIL); + }/* End IF */ + }/* End IF */ + + else { + while(i++ && lbp){ + lbp = lbp->prev_line; + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with B"); + return(FAIL); + }/* End IF */ + j -= lbp->byte_count; + }/* End WHILE */ + }/* End Else */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = hbp->dot; + + hbp->dot += j; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to kill the specified number of lines. + */ + case EXEC_C_KILL: + {/* Local Block */ + register struct buff_header *hbp; + register struct buff_line *lbp; + register int i,j; + int pos1,pos2; + + hbp = curbuf; +/* + * If two arguments were specified, then he has specified an a,b range to + * be killed. + */ + if(ct->ctx.iarg1_flag == YES && ct->ctx.iarg2_flag == YES){ + pos1 = ct->ctx.iarg1; + pos2 = ct->ctx.iarg2; + if(parse_illegal_buffer_position(pos1,pos2,"K")){ + return(FAIL); + }/* End IF */ + if(pos2 < pos1){ + pos2 = ct->ctx.iarg1; + pos1 = ct->ctx.iarg2; + }/* End IF */ + }/* End IF */ +/* + * Else, if it was a single command, it is a relative kill which affects a + * portion of the buffer in a similar way as if it was an L command. Our job + * now is to turn it into a a,b range. + */ + else { + lbp = buff_find_line(hbp,hbp->dot); +/* + * Start by pretending it was a 0L command, and get to the begining of + * the line. That allows us to not worry about offset onto current line. + */ + j = 0 - buff_find_offset(hbp,lbp,hbp->dot); + i = ct->ctx.iarg1; + if(ct->ctx.iarg1_flag == NO) i = 1; +/* + * If argument is positive, movement is forward + */ + if(i > 0){ + while(i-- && lbp){ + j += lbp->byte_count; + lbp = lbp->next_line; + }/* End WHILE */ + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with K"); + return(FAIL); + }/* End IF */ + pos1 = hbp->dot; + pos2 = hbp->dot + j; + }/* End IF */ + + else { + + while(i++ && lbp){ + lbp = lbp->prev_line; + if(lbp == NULL){ + error_message("?Attempt to Move Pointer Off Page with K"); + return(FAIL); + }/* End IF */ + j -= lbp->byte_count; + }/* End WHILE */ + pos1 = hbp->dot + j; + pos2 = hbp->dot; + }/* End Else */ + }/* End Else */ + + j = pos2 - pos1; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = hbp->dot; + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(j) buff_delete_with_undo(uct,hbp,pos1,j); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to insert a character as part of an insert command + */ + case EXEC_C_INSERT: + buff_insert_char_with_undo( + uct, + curbuf, + curbuf->dot, + ct->ctx.iarg1_flag ? ct->ctx.iarg1 : ct->input_byte + ); + return(SUCCESS); +/* + * This state is used when a subroutine state is trying to return a + * value to the calling state. It gets specially noticed by the parser, + * so it really doesn't do much except act as a flag. + */ + case EXEC_C_STOREVAL: + return(SUCCESS); +/* + * Store the current expression value as the first argument to a command + */ + case EXEC_C_STORE1: + ct->ctx.iarg1 = ct->ctx.tmpval; + return(SUCCESS); +/* + * Store the current expression value as the second argument to a command + */ + case EXEC_C_STORE2: + ct->ctx.iarg2 = ct->ctx.tmpval; + return(SUCCESS); +/* + * Here when we have a leading minus sign in an expression such as -3+4 + */ + case EXEC_C_UMINUS: + ct->ctx.tmpval = 0 - ct->ctx.tmpval; + return(SUCCESS); +/* + * Here to add the two parts of the expression to produce a new temporary + * value. + */ + case EXEC_C_PLUS: + ct->ctx.tmpval = ct->ctx.iarg2 + ct->ctx.tmpval; + return(SUCCESS); +/* + * Here to subtract the two parts of the expression to produce a new temporary + * value. + */ + case EXEC_C_MINUS: + ct->ctx.tmpval = ct->ctx.iarg2 - ct->ctx.tmpval; + return(SUCCESS); +/* + * Here to multiply the two parts of the expression to produce a new temporary + * value. + */ + case EXEC_C_TIMES: + ct->ctx.tmpval = ct->ctx.iarg1 * ct->ctx.tmpval; + return(SUCCESS); +/* + * Here to divide the two parts of the expression to produce a new temporary + * value. + */ + case EXEC_C_DIVIDE: + if(ct->ctx.tmpval == 0){ + error_message("?Attempt to Divide by Zero"); + return(FAIL); + }/* End IF */ + ct->ctx.tmpval = ct->ctx.iarg1 / ct->ctx.tmpval; + return(SUCCESS); +/* + * Here in response to the "A" command which returns as a value the ASCII + * value of the character at 'dot' + offset. + */ + case EXEC_C_ACOMMAND: + {/* Local Block */ + register int i; + + i = curbuf->dot + ct->ctx.tmpval; + + if(parse_illegal_buffer_position(i,i+1,"A")){ + return(FAIL); + }/* End IF */ + + ct->ctx.tmpval = buff_contents(curbuf,i); + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to cause a repaint of the screen incase it got messed up by + * something + */ + case EXEC_C_REDRAW_SCREEN: + screen_redraw(); + return(SUCCESS); +/* + * Here to search for a given string. This gets used not only in the + * "S" command, but as a substate in any command which requires searching + * for a string (such as FD, FK, FS, FR, etc.) + */ + case EXEC_C_SEARCH: + {/* Local Block */ + int arg1,arg2; +#if 0 + struct buff_header *qbp; +#endif + + if(set_search_string_with_undo(ct->ctx.carg,uct) == FAIL){ + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + + arg1 = 1; + arg2 = -1; + if(ct->ctx.iarg1_flag) arg1 = ct->ctx.iarg1; + if(ct->ctx.iarg2_flag) arg2 = ct->ctx.iarg2; + +/* + * If there are two arguments, then it is a constrained search and we must + * insure that the two positions specified are valid buffer positions. + */ + if(ct->ctx.iarg1_flag && ct->ctx.iarg2_flag){ + if(arg1 < 0 || arg1 > curbuf->zee){ + sprintf(tmp_message, + "?Illegal buffer position %d in search command",arg1); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + if(arg2 < 0 || arg2 > curbuf->zee){ + sprintf(tmp_message, + "?Illegal buffer position %d in search command",arg2); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + }/* End IF */ + +/* + * That done, we call the search command to perform the search + */ + if(cmd_search(arg1,arg2,&search_string) == FAIL){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + + if(!ct->ctx.inest && search_string.error_message_given == NO){ + sprintf(tmp_message, + "?Cannot find '%.40s'",search_string.input); + error_message(tmp_message); + }/* End IF */ + + return(SUCCESS); + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to implement the FR command. The FR command is similar to the FS + * command (Find-Substitute), except that if the second string argument is + * null, rather than simply deleting the first string argument, it is replaced + * by the default replace string. + */ + case EXEC_C_FRREPLACE: + {/* Local Block */ + register struct buff_header *qbp; + register int j; + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + + j = last_search_pos2 - last_search_pos1; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); + + qbp = buff_qfind('-',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + + buff_insert_from_buffer_with_undo( + uct, + curbuf, /* destination is current edit buf */ + last_search_pos1, /* at the search position */ + qbp, /* source is replacement q-register*/ + 0, /* from position 0 to the end */ + qbp->zee + ); + + if(ct->ctx.iarg1_flag == YES){ + if( ct->ctx.iarg1 < 0){ + curbuf->dot = last_search_pos1; + }/* End IF */ + else if(ct->ctx.iarg2_flag == YES){ + if(ct->ctx.iarg2 < ct->ctx.iarg1){ + curbuf->dot = last_search_pos1; + }/* End IF */ + }/* End Else */ + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ +/* + * Here to implement the FS (Find-Substitute) command which searches for + * the first string argument and replaces it with the second. + */ + case EXEC_C_FSREPLACE1: + {/* Local Block */ + register int j; + register struct buff_header *qbp; + + ct->ctx.tmpval = 0; + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + +#ifdef INTERACTIVE_FS + j = last_search_pos2 - last_search_pos1; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); +#endif /* INTERACTIVE_FS */ + + return(SUCCESS); + + case EXEC_C_FSREPLACE2: + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + + qbp = buff_qfind('-',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + +#ifdef INTERACTIVE_FS + + buff_insert_char_with_undo(uct,curbuf,curbuf->dot,ct->input_byte); + +#endif /* INTERACTIVE_FS */ + +/* + * What this does is hope that the replacement string is the same as the + * previous one. Only if we get a miscompare do we delete the contents of + * the replacement q-register, and insert the new string. + */ + if(qbp->zee > ct->ctx.tmpval){ + if(ct->input_byte == buff_contents(qbp,ct->ctx.tmpval)){ + ct->ctx.tmpval += 1; + return(SUCCESS); + } + + buff_delete_with_undo(uct,qbp,ct->ctx.tmpval, + qbp->zee-ct->ctx.tmpval); + + } + + buff_insert_char_with_undo(uct,qbp,ct->ctx.tmpval,ct->input_byte); + + ct->ctx.tmpval += 1; + return(SUCCESS); + + case EXEC_C_FSREPLACE3: + + qbp = buff_qfind('-',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + + if(qbp->zee > ct->ctx.tmpval){ + buff_delete_with_undo(uct,qbp,ct->ctx.tmpval, + qbp->zee-ct->ctx.tmpval); + } + +#ifndef INTERACTIVE_FS + j = last_search_pos2 - last_search_pos1; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); + + buff_insert_from_buffer_with_undo(uct, + curbuf, + curbuf->dot, + qbp, + 0, + qbp->zee + ); + +#endif /* !INTERACTIVE_FS */ + + if(ct->ctx.iarg1_flag == YES){ + if( ct->ctx.iarg1 < 0){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + curbuf->dot = last_search_pos1; + }/* End IF */ + else if(ct->ctx.iarg2_flag == YES){ + if(ct->ctx.iarg2 < ct->ctx.iarg1){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + curbuf->dot = last_search_pos1; + }/* End IF */ + }/* End Else */ + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = last_search_status; + }/* End IF */ + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to execute the FT (tags) command. Since this is a fairly complicated + * command, we call a subroutine to perform the actions. + */ + case EXEC_C_FTAGS: + {/* Local Block */ + register int arg_count = 0; + register int status; + + if(ct->ctx.iarg1_flag == YES) arg_count = 1; + if(ct->ctx.iarg2_flag == YES) arg_count = 2; + + status = cmd_tags( + uct, + arg_count, + ct->ctx.iarg1, + ct->ctx.iarg2, + ct->ctx.carg + ); + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = status; + return(SUCCESS); + }/* End IF */ + + return(status); + + }/* End Local Block */ + +/* + * Here to execute the FD command which searches for a string and deletes + * it. It is like FS but saves you from typing the second escape. + */ + case EXEC_C_FDCOMMAND: + {/* Local Block */ + register int j; + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + + j = last_search_pos2 - last_search_pos1; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); + + curbuf->dot = last_search_pos1; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ +/* + * Here to implement the FK command. The FK command remembers the current + * position in the buffer and searches for the specified string. It then + * deletes the characters from the old current position up to but not + * including the search string. + */ + case EXEC_C_FKCOMMAND: + {/* Local Block */ + register int j; + + if(last_search_status == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + if(ct->ctx.inest != 0) return(SUCCESS); + return(FAIL); + }/* End IF */ + + if(remembered_dot <= last_search_pos1){ + last_search_pos2 = last_search_pos1; + last_search_pos1 = remembered_dot; + }/* End IF */ + + else { + last_search_pos1 = last_search_pos2; + last_search_pos2 = remembered_dot; + curbuf->dot = last_search_pos1; + }/* End IF */ + + j = last_search_pos2 - last_search_pos1; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = curbuf->dot; + +/* + * Call the standard routine which deletes the specified buffer positions + * and constructs the undo list so that it can be rebuilt if necessary. + */ + if(j) buff_delete_with_undo(uct,curbuf,last_search_pos1,j); + + curbuf->dot = last_search_pos1; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + }/* End Local Block */ +/* + * This state is used by the FK command to remember the current dot + * position before it is changed by the search command. + */ + case EXEC_C_REMEMBER_DOT: + remembered_dot = curbuf->dot; + return(SUCCESS); +/* + * Here to implement the N search which searches across edit buffers for + * the given string. + */ + case EXEC_C_NSEARCH: + {/* Local Block */ + int count; + struct buff_header *original_buffer; + int original_dot; + int saved_dot,saved_top,saved_bottom; + int first_buffer; + int last_buffer = 0; + char back_in_original_buffer; +/* + * We set the search string, just like all the other search commands + */ + if(set_search_string_with_undo(ct->ctx.carg,uct) == FAIL){ + return(FAIL); + }/* End IF */ +/* + * Remember the buffer that we started in, and be ready to change back to + * it if we undo. + */ + original_buffer = curbuf; + original_dot = curbuf->dot; + + count = 1; + if(ct->ctx.iarg1_flag) count = ct->ctx.iarg1; + +/* + * If he says 1,5n$ then we search for the string in buffers 1 + * through 5. We check that both buffers exist before we begin the search. + */ + if(ct->ctx.iarg2_flag){ + count = 1; + first_buffer = ct->ctx.iarg1; + last_buffer = ct->ctx.iarg2; + + if(first_buffer != curbuf->buffer_number){ + if(buff_openbuffer(NULL,last_buffer,0) != SUCCESS){ + return(FAIL); + }/* End IF */ + if(buff_openbuffer(NULL,first_buffer,0) != SUCCESS){ + return(FAIL); + }/* End IF */ + curbuf->dot = 0; + }/* End IF */ + }/* End IF */ + +/* + * In the starting buffer, we want to search from 'dot' to the end. Then + * we would search all the other buffers, and then the original one again, + * but from the top to 'dot'. The saved_dot stuff is necessary because + * the cmd_search routine has the side effect of changing 'dot' in the + * buffer being searched. However, we only want to affect 'dot' in the + * final buffer that the final occurance of the search string is found in. + */ + saved_dot = curbuf->dot; + saved_top = curbuf->dot; + saved_bottom = curbuf->zee; + back_in_original_buffer = 0; + + while(count > 0){ + + if(cmd_search(saved_top,saved_bottom,&search_string) != FAIL){ + saved_top = curbuf->dot; + count -= 1; + continue; + }/* End IF */ +/* + * Here if the search failed. We want to switch over to the next buffer and + * try again, until we exhaust all the buffers. If we reach the final buffer + * without finding the string, we return an error to the user, leaving him + * back in his original buffer. + */ + if(ct->ctx.iarg2_flag && curbuf->buffer_number == last_buffer){ + if(buff_openbuffer(NULL,original_buffer->buffer_number,0) != SUCCESS){ + error_message("Boom! lost original buffer"); + return(FAIL); + }/* End IF */ + back_in_original_buffer = 1; + }/* End IF */ + + if(back_in_original_buffer){ + curbuf->dot = saved_dot; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + sprintf(tmp_message, + "?Cannot find '%.40s'",search_string.input); + if(!ct->ctx.inest && !intr_flag && + search_string.error_message_given == NO){ + error_message(tmp_message); + }/* End IF */ + return(SUCCESS); + }/* End IF */ +/* + * We havn't tried all the buffers yet, switch to the next one. + */ + curbuf->dot = saved_dot; + + while(1){ + curbuf = curbuf->next_header; + if(curbuf == NULL) curbuf = buffer_headers; + if(curbuf->buffer_number == -1) break; + if(curbuf->buffer_number > 0) break; + if(ct->ctx.iarg2_flag) continue; + if(curbuf == original_buffer) break; + }/* End While */ + + saved_dot = curbuf->dot; + saved_top = 0; + saved_bottom = curbuf->zee; + + if(ct->ctx.iarg2_flag == NO){ + if(curbuf->buffer_number == original_buffer->buffer_number){ + back_in_original_buffer = 1; + saved_bottom = original_dot; + }/* End IF */ + }/* End IF */ + + }/* End While */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = original_buffer->buffer_number; + + buff_switch(curbuf,1); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEDOT; + ut->iarg1 = saved_dot; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = -1; + }/* End IF */ + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to begin an iteration < ... > + */ + case EXEC_C_ITERATION_BEGIN: + {/* Local Block */ + register struct cmd_token *oct; + if(ct->ctx.iarg1_flag == YES){ + if(ct->ctx.iarg1 <= 0){ + oct = ct; + while((oct = oct->next_token)){ + if(oct->opcode != TOK_C_ITERATION_END) continue; + if(oct->ctx.inest != (ct->ctx.inest-1)) continue; + jump_to_token = oct; + return(SUCCESS); + }/* End While */ + ct->ctx.go_flag = NO; + return(SUCCESS); + }/* End IF */ + ct->ctx.iarg1 -= 1; + }/* End IF */ + return(SUCCESS); + }/* End Local Block */ +/* + * Here to provide the execution code for the end of an iteration. + * This must search backwards in the parse tokens to find the begining + * of the iteration, and set up to jump to that token + */ + case EXEC_C_ITERATION_END: + {/* Local Block */ + register struct cmd_token *oct; +/* + * The parser left us a pointer to the begining of the iteration so we can + * find it without having to search. + */ + oct = ct->ctx.caller_token; + + if(oct->ctx.iarg1_flag == YES){ + if(oct->ctx.iarg1 <= 0){ + return(SUCCESS); + }/* End IF */ + }/* End IF */ +/* + * The following line grabs the old iteration value so that when the + * begining of iteration code grabs the "previous" iarg1, this will + * be it. Think of it this way: the code has to do something different + * the first time into the iteration (it has to load the results of the + * STORE1, then the subsequent times it has to decrement what's already + * there. That's why this seems a little kludgy. + */ + ct->ctx.iarg1 = oct->ctx.iarg1; + jump_to_token = oct; + return(SUCCESS); + }/* End Local Block */ +/* + * Here to implement the semicolon command which is used to jump out + * of an iteration if the argument is >= 0. + */ + case EXEC_C_SEMICOLON: + {/* Local Block */ + register struct cmd_token *oct; + int value; + + value = last_search_status; + if(ct->ctx.iarg1_flag == YES) value = ct->ctx.iarg1; + + if(value < 0) return(SUCCESS); + + oct = ct; + while((oct = oct->next_token)){ + if(oct->opcode != TOK_C_ITERATION_END) continue; + if(oct->ctx.inest != (ct->ctx.inest-1)) continue; + if(oct->next_token){ + jump_to_token = oct->next_token; + return(SUCCESS); + }/* End IF */ + }/* End While */ + + ct->ctx.go_flag = NO; + return(BLOCKED); + + }/* End Local Block */ +/* + * Here to implement the various conditional operators + */ + case EXEC_C_COND_GT: + if(ct->ctx.iarg1 <= 0){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_LT: + if(ct->ctx.iarg1 >= 0){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_EQ: + if(ct->ctx.iarg1 != 0){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_NE: + if(ct->ctx.iarg1 == 0){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_DIGIT: + if(!isdigit(ct->ctx.iarg1)){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_ALPHA: + if(!isalpha(ct->ctx.iarg1)){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_LOWER: + if(!islower(ct->ctx.iarg1)){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_UPPER: + if(!isupper(ct->ctx.iarg1)){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_COND_SYMBOL: + if(!isalnum(ct->ctx.iarg1) && ct->ctx.iarg1 != '_'){ + return(find_conditional_else(ct)); + }/* End IF */ + return(SUCCESS); + + case EXEC_C_SKIP_ELSE: + return(find_conditional_end(ct)); +/* + * Here to implement the GOTO command OLABEL$ + */ + case EXEC_C_GOTO: + {/* Local Block */ + struct cmd_token *goto_ptr; + struct cmd_token *test_ptr; +/* + * First step is to find the begining of this goto string + */ + goto_ptr = ct; + while((goto_ptr = goto_ptr->prev_token)){ + if(goto_ptr->opcode != TOK_C_GOTO_BEGIN) continue; + break; + }/* End While */ +/* + * Now try to find the label. First search backwards for it + */ + test_ptr = goto_ptr; + while((test_ptr = test_ptr->prev_token)){ + if(test_ptr->opcode != TOK_C_LABEL_BEGIN) continue; + if(compare_label(goto_ptr,test_ptr) == YES){ + ct->ctx.go_flag = NO; + jump_to_token = test_ptr; + return(SUCCESS); + }/* End IF */ + }/* End While */ +/* + * The label is not in front of us, look for it afterwards + */ + test_ptr = goto_ptr; + while((test_ptr = test_ptr->next_token)){ + if(test_ptr->opcode == TOK_C_FINALTOKEN){ + char string1[PARSER_STRING_MAX]; + + extract_label(goto_ptr,string1); + (void) sprintf(tmp_message,"?Can't find label <%s>", + string1); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + if(test_ptr->opcode != TOK_C_LABEL_BEGIN) continue; + + if(compare_label(goto_ptr,test_ptr) == YES){ + jump_to_token = test_ptr; + return(SUCCESS); + }/* End IF */ + }/* End While */ + + ct->ctx.go_flag = NO; + return(BLOCKED); + + }/* End Local Block */ +/* + * Here to write out the buffer. If the filename is specified, write it + * to that name, otherwise write it to the default name of the buffer. + */ + case EXEC_C_WRITEFILE: + {/* Local Block */ + int status; + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + char *filename; + + if(curbuf->isreadonly){ + error_message("?File is Readonly"); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + if(ct->ctx.carg) filename = ct->ctx.carg; + else filename = curbuf->name; + + np = expand_filename(filename); + if(np == NULL){ + sprintf(tmp_message,"EW No such file <%s>",filename); + error_message(tmp_message); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + if(np->we_next){ + sprintf(tmp_message,"EW: Non-unique filename <%s>",filename); + error_message(tmp_message); + while(np){ + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + status = cmd_write(curbuf,np->we_name); + tec_release(TYPE_C_WILD,(char *)np); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = status; + return(SUCCESS); + }/* End IF */ + return(status); + + }/* End Local Block */ +/* + * Read in the named file to the current position in the edit buffer + */ + case EXEC_C_READFILE: + {/* Local Block */ + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + + if(ct->ctx.carg == NULL || ct->ctx.carg[0] == '\0'){ + error_message("?ER Requires a filename"); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)curbuf; + ut->iarg1 = curbuf->dot; + ut->iarg2 = curbuf->zee; + + np = expand_filename(ct->ctx.carg); + if(np == NULL){ + sprintf(tmp_message,"ER No such file <%s>",ct->ctx.carg); + error_message(tmp_message); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + return(FAIL); + }/* End IF */ + + while(np){ + buff_read(curbuf,np->we_name); + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + + + ut->iarg2 = curbuf->zee - ut->iarg2; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + return(SUCCESS); + }/* End IF */ + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to change the current edit buffer. If a numeric argument is + * supplied, change to that buffer, otherwise a string should have been + * specified, and this is the name of the file/buffer to be loaded. + */ + case EXEC_C_EDITBUF: + {/* Local Block */ + struct buff_header *hbp,*old_buffer,*first_buffer; + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + +/* + * Here if there is no string argument. It better be a numeric buffer + * number argument. If so, switch to that buffer, and set the undo to + * switch back to the old one if this gets undone. + */ + if(ct->ctx.carg == NULL || ct->ctx.carg[0] == '\0'){ + if(ct->ctx.iarg1_flag == NO){ + error_message("?EB Requires a filename or argument"); + return(FAIL); + }/* End IF */ + + old_buffer = curbuf; + if(buff_openbuffer(0,ct->ctx.iarg1,0) == FAIL){ + return(FAIL); + }/* End IF */ + + if(curbuf->buffer_number != ct->ctx.iarg1){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + } + return(FAIL); + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = old_buffer->buffer_number; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + } + return(SUCCESS); + + }/* End IF */ + + old_buffer = curbuf; + first_buffer = NULL; + + np = expand_filename(ct->ctx.carg); + if(np == NULL){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + } + sprintf(tmp_message,"EB No such file <%s>",ct->ctx.carg); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + while(np){ +/* + * The following hack detects whether a new buffer was created so that + * we know whether or not to set up an undo to un-create it. + */ + hbp = buff_find(np->we_name); + if(buff_openbuffer(np->we_name,0,0) == SUCCESS){ + if(hbp == NULL){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CLOSEBUFF; + ut->carg1 = (char *)curbuf; + }/* End IF */ + if(first_buffer == NULL) first_buffer = curbuf; + }/* End IF */ + + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = old_buffer->buffer_number; + + buff_openbuffer(0,first_buffer->buffer_number,0); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + return(SUCCESS); + } + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to implement the EF command which removes an edit buffer. + */ + case EXEC_C_CLOSEBUF: + {/* Local Block */ + register struct buff_header *hbp,*bp; + register int i; + + hbp = curbuf; + if(curbuf->buffer_number == 0){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + error_message("?EF Not allowed on special buffers"); + return(FAIL); + }/* End IF */ + + if(hbp->ismodified && !hbp->isreadonly){ + if(ct->ctx.iarg1_flag == NO || + ct->ctx.iarg1 != -1){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + }/* End IF */ + sprintf(tmp_message,"?EF Buffer %s is modified", + curbuf->name); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + }/* End IF */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = hbp->buffer_number; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_REOPENBUFF; + ut->iarg1 = hbp->buffer_number; + ut->carg1 = (char *)hbp; + +/* + * Unlink this buffer header from the header list. Check to see if it is + * at the head of the list. If not at the head of the list, we have to + * search the list till we find it's father. Then we make it's father's + * child be it's child. + */ + i = 0; + bp = buffer_headers; + if(bp == hbp) buffer_headers = bp->next_header; + else { + while(bp){ + if(bp->next_header == hbp) break; + bp = bp->next_header; + }/* End While */ + bp->next_header = hbp->next_header; + curbuf = NULL; + if(bp->buffer_number > 0 && hbp->buffer_number > 0){ + i = bp->buffer_number; + }/* End IF */ + else if(bp->buffer_number < 0 && hbp->buffer_number < 0){ + i = bp->buffer_number; + }/* End Else */ + else if(hbp->buffer_number > 0 && hbp->next_header){ + i = hbp->next_header->buffer_number; + }/* End Else */ + else i = -1; + }/* End Else */ +/* + * Pick a different buffer to be the current buffer + */ + if(i){ + if(buff_openbuffer(NULL,i,0) == FAIL) i = 0; + }/* End IF */ + if(!i){ + if(buff_openbuffer(NULL,-1,0) == FAIL){ + buff_openbuffer(NULL,0,0); + }/* End IF */ + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + }/* End IF */ + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to implement the EV command which is identical to the EB command + * except that if the buffer does not exist, when it is created it is created + * as a read only buffer. + */ + case EXEC_C_VIEWBUF: + {/* Local Block */ + struct buff_header *hbp,*old_buffer,*first_buffer; + struct wildcard_expansion *name_list,*np; + struct wildcard_expansion *expand_filename(); + +/* + * Here if there is no string argument. It better be a numeric buffer + * number argument. If so, switch to that buffer, and set the undo to + * switch back to the old one if this gets undone. + */ + if(ct->ctx.carg == NULL || ct->ctx.carg[0] == '\0'){ + if(ct->ctx.iarg1_flag == NO){ + error_message("?EV Requires a filename or argument"); + return(FAIL); + }/* End IF */ + + old_buffer = curbuf; + buff_openbuffer(0,ct->ctx.iarg1,1); + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = old_buffer->buffer_number; + + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + } + return(SUCCESS); + + }/* End IF */ + + old_buffer = curbuf; + first_buffer = NULL; + + np = expand_filename(ct->ctx.carg); + if(np == NULL){ + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = FAIL; + return(SUCCESS); + } + sprintf(tmp_message,"EV No such file <%s>",ct->ctx.carg); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + while(np){ +/* + * The following hack detects whether a new buffer was created so that + * we know whether or not to set up an undo to un-create it. + */ + hbp = buff_find(np->we_name); + if(buff_openbuffer(np->we_name,0,1) == SUCCESS){ + if(hbp == NULL){ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CLOSEBUFF; + ut->carg1 = (char *)curbuf; + }/* End IF */ + if(first_buffer == NULL) first_buffer = curbuf; + }/* End IF */ + + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,(char *)name_list); + }/* End While */ + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = old_buffer->buffer_number; + + buff_openbuffer(0,first_buffer->buffer_number,1); + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.tmpval = SUCCESS; + return(SUCCESS); + } + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to scroll the screen the specified number of lines + */ + case EXEC_C_SCROLL: + if(ct->ctx.iarg1_flag == NO) ct->ctx.iarg1 = 1; + screen_scroll(ct->ctx.iarg1); + return(SUCCESS); +/* + * Here to cause the screen to update + */ + case EXEC_C_UPDATE_SCREEN: + screen_format_windows(); + screen_refresh(); + return(SUCCESS); +/* + * Here to either split or combine windows + */ + case EXEC_C_WINDOW_CONTROL: + {/* Local Block */ + struct window *old_wptr; + struct window *new_wptr; + + if(ct->ctx.iarg1_flag == NO) screen_delete_window(curwin); + + if(ct->ctx.iarg1_flag == YES && ct->ctx.iarg2_flag == YES){ + old_wptr = curwin; + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + + new_wptr = + screen_split_window( + curwin, + ct->ctx.iarg1, + ct->ctx.iarg2 + ); + + if(new_wptr == NULL) return(FAIL); + + ut->opcode = UNDO_C_WINDOW_SPLIT; + ut->iarg1 = old_wptr->win_window_number; + ut->carg1 = (char *)new_wptr; + }/* End Else */ + + return(SUCCESS); + }/* End Local Block */ +/* + * Here to select the next window as active + */ + case EXEC_C_NEXT_WINDOW: + {/* Local Block */ + int status; + int new_window; + + new_window = ct->ctx.iarg1_flag == YES ? ct->ctx.iarg1 : 0; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_WINDOW_SWITCH; + ut->iarg1 = curwin->win_window_number; + + status = window_switch(new_window); + + return(status); + + }/* End Local Block */ +/* + * Here to reset a ^A message to NULL + */ + case EXEC_C_RESET_MESSAGE: + user_message[0] = '\0'; + return(SUCCESS); +/* + * Here to add a byte to the length of the current user message string + */ + case EXEC_C_MESSAGE: + {/* Local Block */ + register int i; + + if(strlen(user_message) > (sizeof(user_message) - 1)){ + error_message("?^A message length exceeded"); + return(FAIL); + }/* End IF */ + + i = strlen(user_message); + user_message[i] = ct->input_byte; + user_message[i+1] = '\0'; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SHORTEN_MESSAGE; + + return(SUCCESS); + + }/* End Local Block */ +/* + * Here to print the current user message to the status line of the screen + */ + case EXEC_C_OUTPUT_MESSAGE: + screen_message(user_message); + screen_refresh(); + return(SUCCESS); +/* + * Here to execute the specified operating system command, and insert the + * generated output into the edit buffer. + */ + case EXEC_C_ECCOMMAND: + {/* Local Block */ + int status; + + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)curbuf; + ut->iarg1 = curbuf->dot; + ut->iarg2 = 0; + + status = cmd_oscmd(ct); + ut->iarg2 = curbuf->dot - ut->iarg1; + return(status); + + }/* End Local Block */ + + case EXEC_C_SET_IMMEDIATE_MODE: + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SET_IMMEDIATE_MODE; + ut->iarg1 = immediate_execute_flag; + + immediate_execute_flag = YES; + if(ct->ctx.iarg1 == 0){ + ct->ctx.go_flag = NO; + immediate_execute_flag = NO; + }/* End IF */ + return(SUCCESS); + + case EXEC_C_OPENBRACE: + {/* Local Block */ + register struct buff_header *qbp; + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind('$',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Set up the undo token to remember the current edit buffer, and then switch + * to the Q-register. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_CHANGEBUFF; + ut->iarg1 = curbuf->buffer_number; + + buff_switch(qbp,1); +/* + * Delete the previous contents of the q-register, but allow it to be + * reconstructed if this gets undone. Note that we don't have to do + * this if there was nothing there. + */ + if(qbp->zee) buff_delete_with_undo(uct,qbp,0,qbp->zee); +/* + * Arrange for it to be deleted if this gets undone. + */ + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); +/* + * Copy the parser command line into the Q-register + */ + parser_dump_command_line(qbp); + + ut->opcode = UNDO_C_DELETE; + ut->carg1 = (char *)qbp; + ut->iarg1 = 0; + ut->iarg2 = qbp->zee; + + return(SUCCESS); + }/* End Local Block */ + + case EXEC_C_CLOSEBRACE: + {/* Local Block */ + register struct buff_header *qbp; + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, that is an error + */ + qbp = buff_qfind('$',0); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ +/* + * Replace the command line with the contents of the Q-register + */ + parser_replace_command_line(qbp); + + return(INVALIDATE); + + }/* End Local Block */ + +/* + * Here to skip a label (this occurs when we run into a label) + */ + case EXEC_C_SKIPLABEL: + while(ct){ + if(ct->opcode == TOK_C_LABEL_END){ + jump_to_token = ct; + return(SUCCESS); + }/* End IF */ + ct = ct->next_token; + }/* End While */ + return(SUCCESS); + +/* + * Here if he wants to set some options up + */ + case EXEC_C_SETOPTIONS: + ut = allocate_undo_token(uct); + if(ut == NULL) return(FAIL); + ut->opcode = UNDO_C_SETOPTIONS; + + return(cmd_setoptions(ct->ctx.iarg1,ct->ctx.iarg2,ut)); + + default: + sprintf(tmp_message,"?TECEXEC: unknown state %d dispatched", + ct->execute_state); + error_message(tmp_message); + return(FAIL); + + }/* End Switch */ + +}/* End Routine */ + + + +/* PUSH_QREGISTER - Push a Q register onto the stack + * + * Function: + * + * This routine is called to make a copy of a Q register and place it + * on the Q register stack. + */ +int +push_qregister(letter) +char letter; +{ +register struct buff_header *hbp; +register struct buff_header *qbp; + + PREAMBLE(); + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, create it. + */ + qbp = buff_qfind(letter,1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + + hbp = buff_duplicate(qbp); + + hbp->next_header = qregister_push_down_list; + qregister_push_down_list = hbp; + + return(SUCCESS); + +}/* End Routine */ + + +/* EXTRACT_LABEL - Build a string with the contents of the label in it + * + * Function: + * + * This routine is called to build a string with the label name + * in it. The current use is for error messages. + */ +void +extract_label(label_ptr,string1) +register struct cmd_token *label_ptr; +char *string1; +{ +register char *cp1; + + cp1 = string1; + *cp1 = '\0'; + while(label_ptr){ + if(cp1 >= &string1[PARSER_STRING_MAX-1]) break; + if(label_ptr->opcode == TOK_C_INPUTCHAR){ + if(label_ptr->input_byte == '!') break; + *cp1++ = label_ptr->input_byte; + *cp1 = '\0'; + }/* End IF */ + if(label_ptr->opcode == TOK_C_LABEL_END) break; + label_ptr = label_ptr->next_token; + }/* End While */ + +}/* End Routine */ + +/* COMPARE_LABEL - Test if this is our label + * + * Function: + * + * This routine is called by the GOTO function to compare labels + * to see if they match. + */ +int +compare_label(goto_ptr,label_ptr) +register struct cmd_token *goto_ptr; +register struct cmd_token *label_ptr; +{ +char string1[PARSER_STRING_MAX],string2[PARSER_STRING_MAX]; +char *cp1; + + PREAMBLE(); + + cp1 = string1; + + *cp1 = '\0'; + while(goto_ptr){ + if(cp1 >= &string1[PARSER_STRING_MAX-1]) break; + if(goto_ptr->opcode == TOK_C_INPUTCHAR){ + if(goto_ptr->input_byte == ESCAPE) break; + *cp1++ = goto_ptr->input_byte; + *cp1 = '\0'; + }/* End IF */ + if(goto_ptr->opcode == TOK_C_GOTO_END) break; + goto_ptr = goto_ptr->next_token; + }/* End While */ + + cp1 = string2; + + *cp1 = '\0'; + while(label_ptr){ + if(cp1 >= &string2[PARSER_STRING_MAX-1]) break; + if(label_ptr->opcode == TOK_C_INPUTCHAR){ + if(label_ptr->input_byte == '!') break; + *cp1++ = label_ptr->input_byte; + *cp1 = '\0'; + }/* End IF */ + if(label_ptr->opcode == TOK_C_LABEL_END) break; + label_ptr = label_ptr->next_token; + }/* End While */ + + if(strcmp(string1,string2) == 0) return(YES); + + return(NO); + +}/* End Routine */ + + + +/* FIND_CONDITIONAL_ELSE - Routine to find the else of a conditional + * + * Function: + * + * This routine is called by the conditional expressions when the + * specified condition has not been met and we want to execute the + * else clause of the conditional. The routine searches until it + * finds either the else, or the end of the conditional. + */ +int +find_conditional_else(ct) +register struct cmd_token *ct; +{ + register struct cmd_token *oct; + + PREAMBLE(); + + oct = ct; + while((oct = oct->next_token)){ + if(oct->opcode == TOK_C_CONDITIONAL_ELSE){ + if(oct->ctx.cnest == ct->ctx.cnest){ + jump_to_token = oct; + return(SUCCESS); + }/* End IF */ + }/* End IF */ + + if(oct->opcode == TOK_C_CONDITIONAL_END){ + if(oct->ctx.cnest == (ct->ctx.cnest-1)){ + jump_to_token = oct; + return(SUCCESS); + }/* End IF */ + }/* End IF */ + + }/* End While */ + ct->ctx.go_flag = NO; + return(BLOCKED); + +}/* End Routine */ + +/* FIND_CONDITIONAL_END - Routine to find the close of a conditional + * + * Function: + * + * This routine is called to skip over the else clause and find the + * end of the conditional. + */ +int +find_conditional_end(ct) +register struct cmd_token *ct; +{ + register struct cmd_token *oct; + + PREAMBLE(); + + oct = ct; + while((oct = oct->next_token)){ + if(oct->opcode != TOK_C_CONDITIONAL_END) continue; + if(oct->ctx.cnest != (ct->ctx.cnest-1)) continue; + jump_to_token = oct; + return(SUCCESS); + }/* End While */ + ct->ctx.go_flag = NO; + return(BLOCKED); + +}/* End Routine */ + + + +/* EXEC_DOQ0 - Execute Q register zero as a macro + * + * Function: + * + * This routine is called to execute Q register zero as a macro on + * startup. This allows the user to initialize things from his teco.ini + * before teco reaches command level. + */ +int +exec_doq0() +{ + register struct buff_header *qbp; + register struct undo_token *ut; + struct cmd_token *ct; + + PREAMBLE(); + +/* + * Get a pointer to the q-register buffer structure. If it doesn't + * exist, declare an error. + */ + qbp = buff_qfind('0',0); + if(qbp == NULL){ + error_message("?Internal Error! Q register 0 disappeared during init"); + return(FAIL); + }/* End IF */ +/* + * If the Q register is empty, just return with no error. + */ + if(qbp->zee == 0){ + return(SUCCESS); + }/* End IF */ +/* + * Set up a fake command token and undo token structure for the parser to + * chain undo blocks off of. We don't ever undo this stuff, but the routine + * expects it to be there. + */ + ct = allocate_cmd_token((struct cmd_token *)NULL); + if(ct == NULL) return(FAIL); + + ut = allocate_undo_token(ct); + if(ut == NULL){ + free_cmd_token(ct); + return(FAIL); + }/* End IF */ + + ut->opcode = UNDO_C_MACRO; + ut->carg1 = NULL; + + tecmacro(qbp,ct,(struct cmd_token **)&ut->carg1); + parser_cleanup_ctlist(ct); + return(SUCCESS); + +}/* End Routine */ diff --git a/tecmem.c b/tecmem.c new file mode 100644 index 0000000..739265b --- /dev/null +++ b/tecmem.c @@ -0,0 +1,456 @@ +char *tecmem_c_version = "tecmem.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecmem.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecmem.c + * Subroutines to manage memory allocation and deallocation + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" + +#if HAVE_SBRK +#ifndef HAVE_UNISTD_H + char *sbrk(); +#endif +#endif + + char *starting_break; + +#ifndef HAVE_UNISTD_H + char *malloc(); + void free(); + void exit(); +#endif + + extern struct buff_header *curbuf; + extern FILE *teco_debug_log; + +struct memblock { + unsigned char type; + int size; +}; + +struct memlist { + unsigned char type; + struct memlist *next_block; +}; + +char *malloc_leftover; +int malloc_leftover_size; +struct memlist *lookaside_lists[LOOKASIDE_COUNT]; +int lookaside_stat_allocated[LOOKASIDE_COUNT]; +int lookaside_stat_available[LOOKASIDE_COUNT]; +int malloc_stat_call_count,malloc_stat_inuse; + +int memstat_by_type[TYPE_C_MAXTYPE-TYPE_C_MINTYPE+1]; + +void tecmem_verify(unsigned char,char *,char *); + +/* TEC_ALLOC - Routine to allocate some memory + * + * Function: + * + * This routine is called to request memory + */ +char * +tec_alloc(type,size) +int type; +int size; +{ +int actual_size; +register int i,j; +register char *cp; +register struct memblock *mp; +register struct memlist *lp; +extern char outof_memory; + + PREAMBLE(); + +/* + * First we check that the caller is requesting a known memory block + * type. This is just a consitency check. + */ + if(type < TYPE_C_MINTYPE || type > TYPE_C_MAXTYPE){ + printf("\nTECO: UNKNOWN MEMORY BLOCK TYPE %d in TEC_ALLOC\n",type); +#ifdef UNIX + kill(getpid(),SIGQUIT); +#endif + exit(ENOMEM); + }/* End IF */ + + +#ifdef INSERT_RANDOM_MALLOC_ERRORS + { + int chance; + chance = rand() % 100; + if(chance == 0){ + return(NULL); + }/* End IF */ + } +#endif /* INSERT_RANDOM_MALLOC_ERRORS */ + +/* + * If the size required is larger than the largest lookaside block we support, + * we just call malloc with it. + */ + actual_size = size + sizeof(struct memblock); + if(actual_size > LARGEST_LOOKASIDE_BLOCK){ + mp = (struct memblock *)malloc((unsigned)actual_size); + + if(mp == NULL){ + outof_memory = YES; + return(NULL); + }/* End IF */ + + malloc_stat_call_count += 1; + malloc_stat_inuse += actual_size; + mp->type = type; + mp->size = actual_size; + cp = (char *)mp + sizeof(struct memblock); + return(cp); + }/* End IF */ + +/* + * Round up to the next larger lookaside block size + */ + if(actual_size & (MINIMUM_ALLOCATION_BLOCK - 1)){ + actual_size += MINIMUM_ALLOCATION_BLOCK; + actual_size &= ~(MINIMUM_ALLOCATION_BLOCK - 1); + }/* End IF */ + +/* + * Check whether we have a lookaside entry of that size. If not, we allocate + * some number of entries of that size from our large memory hunk. + */ + i = (actual_size / MINIMUM_ALLOCATION_BLOCK) - 1; + + if(lookaside_lists[i] == NULL){ + tec_gc_lists(); + }/* End IF */ + + if(lookaside_lists[i] == NULL){ + if(malloc_leftover != NULL){ + if(malloc_leftover_size < actual_size){ + i = (malloc_leftover_size / MINIMUM_ALLOCATION_BLOCK) - 1; + lp = (struct memlist *)malloc_leftover; + lp->next_block = lookaside_lists[i]; + lookaside_lists[i] = lp; + lookaside_stat_available[i] += 1; + i = (actual_size / MINIMUM_ALLOCATION_BLOCK) - 1; + malloc_leftover = NULL; + malloc_leftover_size= 0; + }/* End IF */ + else { + lp = (struct memlist *)malloc_leftover; + malloc_leftover += actual_size; + malloc_leftover_size -= actual_size; + if(malloc_leftover_size == 0) malloc_leftover = NULL; + lp->next_block = NULL; + lookaside_lists[i] = lp; + lookaside_stat_available[i] += 1; + }/* End Else */ + }/* End IF */ + }/* End IF */ + + if(lookaside_lists[i] == NULL){ + j = BIG_MALLOC_HUNK_SIZE / actual_size; + cp = malloc((unsigned)(BIG_MALLOC_HUNK_SIZE)); + + if(cp == NULL){ + printf("\nTECO: malloc failed!\n"); + outof_memory = YES; + return(NULL); + }/* End IF */ + +/* + * Place the one block on the lookaside list, and the rest as a malloc + * leftover. + */ + lp = (struct memlist *)cp; + lookaside_stat_available[i] += 1; + lp->next_block = NULL; + lookaside_lists[i] = lp; + malloc_leftover = cp + actual_size; + malloc_leftover_size = BIG_MALLOC_HUNK_SIZE - actual_size; + + }/* End IF */ + + lp = lookaside_lists[i]; + lookaside_lists[i] = lp->next_block; + lookaside_stat_allocated[i] += 1; + lookaside_stat_available[i] -= 1; + memstat_by_type[type-TYPE_C_MINTYPE] += 1; + + mp = (struct memblock *)lp; + mp->type = type; + mp->size = actual_size; + cp = (char *)mp + sizeof(struct memblock); + bzero(cp,size); + return(cp); + +}/* End Routine */ + +/* TEC_RELEASE - Envelope routine for free() + * + * Function: + * + * This routine is called to release memory which was previously allocated + * by calling the tec_alloc routine. + */ +void +tec_release(type,addr) +unsigned char type; +register char *addr; +{ +register struct memblock *mp; +register struct memlist *lp; +register int i; + + PREAMBLE(); + + mp = (struct memblock *)( addr - sizeof(struct memblock) ); + if(mp->type != type){ + printf("\nTEC_RELEASE: TYPE Mismatch: Supplied %d Stored %d addr 0x%x\n", + type,mp->type,(unsigned int)addr); +#ifdef UNIX + kill(getpid(),SIGQUIT); +#endif + exit(ENOMEM); + }/* End IF */ + + if(mp->size > LARGEST_LOOKASIDE_BLOCK){ + malloc_stat_inuse -= mp->size; + free(mp); + return; + }/* End IF */ + + mp->type += 1000; + i = mp->size / MINIMUM_ALLOCATION_BLOCK - 1; + lp = (struct memlist *)mp; + lp->next_block = lookaside_lists[i]; + lookaside_lists[i] = lp; + + lookaside_stat_available[i] += 1; + lookaside_stat_allocated[i] -= 1; + memstat_by_type[type-TYPE_C_MINTYPE] -= 1; + + return; + +}/* End Routine */ + + + +/* TECMEM_VERIFY - Verify that structure type hasn't been corrupted + * + * Function: + * + * This debug routine checks to see whether the memory block has been + * overwritten by checking the type code. + */ +void +tecmem_verify(type,addr,message) +unsigned char type; +register char *addr; +char *message; +{ +register struct memblock *mp; +#if 0 +register struct memlist *lp; +#endif +#if 0 +register int i; +#endif + + PREAMBLE(); + + mp = (struct memblock *)( addr - sizeof(struct memblock) ); + if(mp->type != type){ + printf( + "\nTYPE Mismatch: Supplied %d Stored %d addr %x '%s'\n", + type,mp->type,(unsigned int)addr,message); + exit(1); + }/* End IF */ + + return; + +}/* End Routine */ + + + +/* TEC_GC_LISTS - Garbage collect any local lookaside lists + * + * Function: + * + * This routine is called before we call malloc for more memory in + * an attempt to find the memory on some local lookaside lists. + */ +void +tec_gc_lists() +{ + + PREAMBLE(); + + screen_deallocate_format_lookaside_list(); + buff_deallocate_line_buffer_lookaside_list(); + +}/* End Routine */ + + +void +initialize_memory_stats() +{ + starting_break = 0; + +#if HAVE_SBRK + if(starting_break == NULL){ + starting_break = sbrk(0); + }/* End IF */ +#endif + +} + + +/* TECMEM_STATS - Insert memory statistics into the current buffer + * + * Function: + * + * This routine is called by the buffer map routine to allow us to + * insert some memory statistics into the map. + */ +void +tecmem_stats() +{ +char tmp_buffer[LINE_BUFFER_SIZE]; +register int i; +register int size; +int total_memory_in_use; +char *current_break; +int bss_in_use; + + PREAMBLE(); + + total_memory_in_use = malloc_stat_inuse; + + sprintf( + tmp_buffer, + "\n\n%d non-lookaside allocations, %d bytes outstanding\n\n", + malloc_stat_call_count, + malloc_stat_inuse + ); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + for(i = 0; i < LOOKASIDE_COUNT; i++){ + if(lookaside_stat_allocated[i] == 0 && + lookaside_stat_available[i] == 0) continue; + size = ( i + 1 ) * MINIMUM_ALLOCATION_BLOCK; + total_memory_in_use += lookaside_stat_allocated[i] * size; + + sprintf( + tmp_buffer, + "LA%2d, size %4d, bytes in use %6d, bytes available %6d\n", + i, + size, + lookaside_stat_allocated[i] * size, + lookaside_stat_available[i] * size + ); + + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + }/* End FOR */ + + sprintf( + tmp_buffer, + "Malloc Leftover %d bytes\n", + malloc_leftover_size + ); + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + buff_insert(curbuf,curbuf->dot,"\n",1); + + for(i = 0; i < TYPE_C_MAXTYPE-TYPE_C_MINTYPE; i++){ + static char *type_name_list[] = { + "CBUFF", + "CPERM", + "CMD", + "UNDO", + "SCR", + "SCRBUF", + "SCREEN", + "SCREENBUF", + "LINE", + "LINEBUF", + "BHDR", + "WINDOW", + "LABELFIELD", + "WILD", + "TAGS", + "TAGENT", + "TAGSTR", + "MAXTYPE" + }; + + if(i >= ELEMENTS(type_name_list)){ + sprintf( + tmp_buffer, + "Unknown memory type index %d(%d)\n", + i, + i+TYPE_C_MINTYPE + ); + }/* End IF */ + + else { + sprintf( + tmp_buffer, + "Type %10s, blocks in use %d\n", + type_name_list[i], + memstat_by_type[i] + ); + }/* End Else */ + + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + + }/* End FOR */ + +#if HAVE_SBRK + current_break = sbrk(0); + if(current_break != sbrk(0)){ + tec_panic("sbrk(0) seems to be allocating space!\n"); + }/* End IF */ + bss_in_use = current_break - starting_break; +#else + bss_in_use = 0; +#endif /* UNIX */ + + if( bss_in_use ){ + sprintf( + tmp_buffer, + "\nTotal memory in use %d, Total allocated bss %d\n", + total_memory_in_use, + bss_in_use + ); + } else { + sprintf(tmp_buffer,"\nTotal memory in use %d\n",total_memory_in_use); + } + buff_insert(curbuf,curbuf->dot,tmp_buffer,strlen(tmp_buffer)); + +}/* End Routine */ diff --git a/teco.c b/teco.c new file mode 100644 index 0000000..b2a9443 --- /dev/null +++ b/teco.c @@ -0,0 +1,1888 @@ +char *teco_c_version = "teco.c: $Revision: 1.1 $"; +char *copyright = "Copyright (c) 1985-2003 Paul Cantrell & Joyce Nishinaga"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/teco.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* teco.c + * Main TECO entry point with most of the initialization code + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +/* + * Global Storage is defined here for lack of a better place. + * First off are all the flags. + */ + char readonly_flag = NO; + char piped_input_flag = NO; + char piped_output_flag = NO; + char tty_modes_saved = NO; + char exit_flag = NO; + char intr_flag = 0; + char susp_flag = 0; + char input_pending_flag = NO; + int tab_width = NORMAL_TAB_WIDTH; + char alternate_escape_character = ESCAPE; + char main_delete_character = RUBOUT; + char alternate_delete_character = RUBOUT; + char eight_bit_output_flag = NO; + char hide_cr_flag = NO; + char waiting_for_input_flag = NO; + char resize_flag = NO; + char teco_startup = YES; + char screen_startup = YES; + char checkpoint_flag = NO; + char checkpoint_enabled = YES; + char checkpoint_modified = NO; + char outof_memory = NO; + char suspend_is_okay_flag = YES; + +/* + * Global Variables + */ + int tty_input_chan; + int tty_output_chan; + int term_speed; + FILE *teco_debug_log; + + int term_columns; + int term_lines; + int forced_width,forced_height; + + int checkpoint_interval = DEFAULT_CHECKPOINT; + + char *output_tty_name; +/* + * Table of Bit Positions + */ + unsigned int IntBits[BITS_PER_INT]; + +/* + * Table of Spaces for tab expansion + */ + char tab_expand[MAX_TAB_WIDTH+1]; +/* + * Global structures + */ +#if HAVE_TERMIOS_H + static struct termios tty_input_modes; + static struct termios tty_output_modes; +#else +#ifdef ATT_UNIX + static struct termio tty_input_modes; + static struct termio tty_output_modes; +#endif +#ifdef BSD_UNIX + static struct sgttyb tty_input_modes; + static struct sgttyb tty_output_modes; +#endif +#endif + +/* + * External Variables + */ + +/* + * Prototypes + */ + void init_signals(void); + void teco_ini(void); + void process_directory(char *,char*,int); + int match_name(char *,char *); + int map_baud(int); + + + +/* TECO - Text Edit and COrrector + * + * Function: + * + * This is the entry point for TECO. It initializes the terminal, reads + * in the specified files, and generally gets things going. + */ +int +main(argc,argv) +int argc; +char **argv; +{ +register int i; + + initialize_memory_stats(); + + for(i = 0; i < BITS_PER_INT; i++){ + IntBits[i] = 1 << i; + }/* End FOR */ + for(i = 0; i < MAX_TAB_WIDTH; i++){ + tab_expand[i] = ' '; + }/* End FOR */ + tab_expand[MAX_TAB_WIDTH] = '\0'; + + if(buff_init() == FAIL){ + tec_error(ENOMEM,"Unable to initialize buffers"); + }/* End IF */ + + handle_command_line(0,argc,argv); + + init_signals(); + initialize_tty(); + + if(strcmp(argv[0],TECO_READONLY_NAME) == 0){ + readonly_flag = YES; + }/* End IF */ + + handle_command_line(1,argc,argv); + + teco_ini(); + + teco_startup = NO; + + while(exit_flag == NO){ + tecparse(); + intr_flag = 0; + }/* End While */ + + if(piped_output_flag){ + extern struct buff_header *curbuf; + buff_openbuffnum(-1,0); + buff_write(curbuf,fileno(stdout),0,curbuf->zee); + }/* End IF */ + + restore_tty(); +/* + * Output a final newline to get the terminal onto a fresh + * line. + */ + term_putc('\n'); + term_flush(); + +#ifdef CHECKPOINT + remove_checkpoint_file(); +#endif /* CHECKPOINT */ + +#ifdef UNIX + return(0); +#endif + +#ifdef VMS + return(STS$M_SUCCESS); +#endif + +}/* End Routine */ + + + +#ifdef UNIX + +/* INITIALIZE_TTY - Set the tty up the way we want it + * + * Function: + * + * This routine is called at startup time to set up the terminal the way + * that we require, i.e., it sets the tty modes. It also gives the screen + * package a chance to initialize itself. + */ +void +initialize_tty() +{ +#if HAVE_TERMIOS_H + struct termios tmp_tty_modes; +#else +#ifdef ATT_UNIX +#define GET_IOCTL TCGETA +#define SET_IOCTL TCSETA + struct termio tmp_tty_modes; +#endif +#ifdef BSD_UNIX +#define GET_IOCTL TIOCGETP +#define SET_IOCTL TIOCSETN +#ifdef INTEGRATED_SOLUTIONS + int mask; +#endif /* INTEGRATED_SOLUTIONS */ + + struct sgttyb tmp_tty_modes; +#endif /* BSD_UNIX */ +#endif /* HAVE_TERMIOS_H */ + +#ifdef SUN_STYLE_WINDOW_SIZING + struct winsize os_window; +#endif + +/* + * We start by assuming that standard input is a tty device. If it turns out + * that it is not, however, then we want to use /dev/tty as the keyboard. + */ + if(!isatty(tty_input_chan = fileno(stdin))){ + tty_input_chan = open("/dev/tty",O_RDONLY); + piped_input_flag = YES; + if(tty_input_chan < 0){ + perror("cannot open /dev/tty"); + punt(errno); + }/* End IF */ + }/* End IF */ +/* + * Standard output MUST by a tty at this time. + */ + if(output_tty_name){ + tty_output_chan = open(output_tty_name,O_RDWR); + }/* End IF */ + + else { + tty_output_chan = fileno(stdout); + }/* End Else */ + + if(!isatty(tty_output_chan)){ + tty_output_chan = open("/dev/tty",O_RDWR); + piped_output_flag = YES; + if(tty_output_chan < 0){ + perror("cannot open /dev/tty"); + punt(errno); + }/* End IF */ + }/* End IF */ +/* + * Get the current modes of the tty so it can be restored later. + */ +#if HAVE_TERMIOS_H + if(tcgetattr(tty_input_chan,&tty_input_modes) < 0){ + perror("could not get input tty modes"); + punt(errno); + }/* End IF */ + + if(tcgetattr(tty_output_chan,&tty_output_modes) < 0){ + perror("could not get output tty modes"); + punt(errno); + }/* End IF */ + +#else + if(ioctl(tty_input_chan,GET_IOCTL,&tty_input_modes) < 0){ + perror("could not get input tty modes"); + punt(errno); + }/* End IF */ + + if(ioctl(tty_output_chan,GET_IOCTL,&tty_output_modes) < 0){ + perror("could not get output tty modes"); + punt(errno); + }/* End IF */ + +#endif + +#if 0 /* Turned off because xterm is getting clobbered by this */ +#if defined(ATT_UNIX) || defined(_POSIX_SOURCE) + if((tty_output_modes.c_cflag & CSIZE) == CS8){ + eight_bit_output_flag = YES; + }/* End IF */ +#endif +#endif + +#if defined(HAVE_DECL_CFGETOSPEED) && HAVE_DECL_CFGETOSPEED + term_speed = map_baud( cfgetospeed(&tty_output_modes) ); +#else +# if defined(HAVE_STRUCT_TERMIOS_C_CFLAG) && defined(CBAUD) + term_speed = map_baud(tty_output_modes.c_cflag & CBAUD); +# else +# if HAVE_STRUCT_TERMIOS_C_OSPEED + term_speed = map_baud(tty_output_modes.c_ospeed); +#else +# if HAVE_STRUCT_TERMIOS_SG_OSPEED + term_speed = map_baud(tty_output_modes.sg_ospeed); +# endif +# endif +# endif +#endif + +#if HAVE_TERMIOS_H + main_delete_character = tty_input_modes.c_cc[VERASE]; +#else +#if defined(BSD_UNIX) + main_delete_character = tty_input_modes.sg_erase; +#endif +#endif + + tty_modes_saved = YES; +/* + * Check out the termcap description for this tty now. It makes + * no sense to go changing the terminal modes all around until + * we decide whether we can run on this terminal or not. + */ + init_term_description(); +/* + * If the OS supports window size ioctls, we try for that since it + * will tend to be more correct for a window environment. + */ +#ifdef SUN_STYLE_WINDOW_SIZING + if(ioctl(tty_output_chan,TIOCGWINSZ,&os_window) >= 0){ + + if((unsigned)os_window.ws_row >= SCREEN_MAX_LINES){ + fprintf(stderr,"os_window.ws_row %d os_window.ws_col %d\n", + (int)os_window.ws_row,(int)os_window.ws_col); + fprintf(stderr,"term_lines %d term_columns %d\n", + term_lines,term_columns); + fprintf(stderr,"forced_height %d forced_width %d\n", + forced_height,forced_width); + os_window.ws_row = term_lines; + os_window.ws_col = term_columns; + }/* End IF */ + + if(forced_height > 0) os_window.ws_row = forced_height; + if(forced_width > 0) os_window.ws_col = forced_width; + if(teco_startup == YES){ + term_lines = os_window.ws_row; + term_columns = os_window.ws_col; + }/* End IF */ + if(term_lines == 0) term_lines = 24; + if(term_columns == 0) term_columns = 80; + if(term_lines != os_window.ws_row || term_columns != os_window.ws_col){ + screen_resize(); + }/* End IF */ + }/* End IF */ +#else + if(forced_height > 0) term_lines = forced_height; + if(forced_width > 0) term_columns = forced_width; + if(term_lines == 0) term_lines = 24; + if(term_columns == 0) term_columns = 80; +#endif + + if(term_lines >= SCREEN_MAX_LINES){ + char tmp_message[LINE_BUFFER_SIZE]; + sprintf( + tmp_message, + "terminal line count of %d exceeds maximum of %d", + term_lines, + SCREEN_MAX_LINES + ); + tec_error(E2BIG,tmp_message); + }/* End IF */ + +/* + * Now set the modes that we want + */ + tmp_tty_modes = tty_input_modes; + +#if HAVE_TERMIOS_H + tmp_tty_modes.c_lflag &= ~(ICANON | ECHO); + tmp_tty_modes.c_cc[VMIN] = 1; + tmp_tty_modes.c_cc[VTIME] = 1; +#else +#ifdef SYS_III_UNIX + tmp_tty_modes.c_lflag &= ~(ICANON | ECHO | ISTATUS); + tmp_tty_modes.c_cc[VMIN] = 1; + tmp_tty_modes.c_cc[VTIME] = 1; +#endif + +#ifdef BSD_UNIX + tmp_tty_modes.sg_flags |= CBREAK; + tmp_tty_modes.sg_flags &= ~(ECHO); +#endif +#endif /* SYSV or POSIX */ + +#if HAVE_TERMIOS_H + if(tcsetattr(tty_input_chan,TCSANOW,&tmp_tty_modes) < 0){ + perror("could not set input tty modes"); + punt(errno); + }/* End IF */ +#else + if(ioctl(tty_input_chan,SET_IOCTL,&tmp_tty_modes) < 0){ + perror("could not set input tty modes"); + punt(errno); + }/* End IF */ +#endif + +#ifdef INTEGRATED_SOLUTIONS + mask = LLITOUT; + ioctl(tty_output_chan,TIOCLBIS,&mask); +#endif + + screen_init(); + +}/* End Routine */ + +#endif /* UNIX */ + +/* END OF UNIX CONDITIONAL CODE */ + + + +#if !HAVE_TCGETATTR + +/* TCGETATTR - Mimic the POSIX terminal get/set functions + * + * Function: + * + * For a system being compiled with POSIX headers, but which doesn't + * actually have all the POSIX functions in libc, we have to fake up + * this call. + */ +int +tcgetattr(fd,termios_p) +int fd; +struct termios *termios_p; +{ +struct termio tmp_modes; +register int i; +int status; + + bzero(&tmp_modes,sizeof(tmp_modes)); + if(status = ioctl(fd,TCGETA,&tmp_modes) < 0){ + return(-1); + }/* End IF */ + + termios_p->c_iflag = tmp_modes.c_iflag; + termios_p->c_oflag = tmp_modes.c_oflag; + termios_p->c_cflag = tmp_modes.c_cflag; + termios_p->c_lflag = tmp_modes.c_lflag; + termios_p->c_line = tmp_modes.c_line; + for(i = 0; i < (NCCS < NCC ? NCCS : NCC); i++){ + termios_p->c_cc[i] = tmp_modes.c_cc[i]; + }/* End FOR */ + + return(status); + +}/* End Routine */ + +#endif /* !HAVE_TCGETATTR */ + +#if !HAVE_TCSETATTR + +/* TCSETATTR - Mimic the POSIX terminal get/set functions + * + * Function: + * + * For a system being compiled with POSIX headers, but which doesn't + * actually have all the POSIX functions in libc, we have to fake up + * this call. + */ +tcsetattr(fd,flags,termios_p) +int fd; +int flags; +const struct termios *termios_p; +{ +struct termio tmp_modes; +register int i; +int status; + + bzero(&tmp_modes,sizeof(tmp_modes)); + tmp_modes.c_iflag = termios_p->c_iflag; + tmp_modes.c_oflag = termios_p->c_oflag; + tmp_modes.c_cflag = termios_p->c_cflag; + tmp_modes.c_lflag = termios_p->c_lflag; + tmp_modes.c_line = termios_p->c_line; + for(i = 0; i < (NCCS < NCC ? NCCS : NCC); i++){ + tmp_modes.c_cc[i] = termios_p->c_cc[i]; + }/* End FOR */ + if(status = ioctl(fd,TCSETA,&tmp_modes) < 0){ + return(-1); + }/* End IF */ + + return(status); + +}/* End Routine */ + +#endif /* !HAVE_TCSETATTR */ + +#ifdef VMS + + +/* INITIALIZE_TTY - Set the tty up the way we want it + * + * Function: + * + * This routine is called at startup time to set up the terminal the way + * that we require, i.e., it sets the tty modes. It also gives the screen + * package a chance to initialize itself. + */ +void +initialize_tty() +{ +register int status; +$DESCRIPTOR(input_name,"SYS$INPUT"); +$DESCRIPTOR(output_name,"SYS$OUTPUT"); + +struct sense_mode_buffer { + unsigned char class; + unsigned char type; + unsigned short buffer_size; + unsigned char characteristics[3]; + unsigned char page_length; +} sense_buffer; + +struct sense_iosb { + unsigned short status; + unsigned char transmit_speed; + unsigned char receive_speed; + unsigned char cr_fill_count; + unsigned char lf_fill_count; + unsigned char parity_flags; + unsigned char unused; +} sense_iosb; + +/* + * Assign channels to the terminal + */ + status = sys$assign(&input_name,&tty_input_chan,0,0); + if(!(status & STS$M_SUCCESS)){ + tec_error(status,"VTECO: ASSIGN of SYS$INPUT Failed"); + }/* End IF */ + +/* + * Standard output MUST by a tty at this time. + */ + status = sys$assign(&output_name,&tty_output_chan,0,0); + if(!(status & STS$M_SUCCESS)){ + tec_error(status,"VTECO: ASSIGN of SYS$OUTPUT Failed"); + }/* End IF */ + +/* + * Get the baud rate of the terminal + */ + status = sys$qiow(0,tty_output_chan,IO$_SENSEMODE, + &sense_iosb,0,0,&sense_buffer,sizeof(sense_buffer),0,0,0,0); + if(!(status & STS$M_SUCCESS)){ + tec_error(status,"VTECO: Error on sense mode %d (%x)",status,status); + }/* End IF */ + + term_lines = sense_buffer.page_length; + term_columns = sense_buffer.buffer_size; + + term_speed = map_baud(sense_iosb.transmit_speed); + tty_modes_saved = YES; + +/* + * Check out the termcap description for this tty now. It makes + * no sense to go changing the terminal modes all around until + * we decide whether we can run on this terminal or not. + */ + init_term_description(); +/* + * Now set the modes that we want + */ + screen_init(); + +}/* End Routine */ + +/* END OF VMS CONDITIONAL CODE */ + +#endif + + + +/* RESTORE_TTY - Set the tty back to the way that it was + * + * Function: + * + * This routine is called at exit time to set the terminal modes back + * how they were when we started. + */ +void +restore_tty() +{ + +/* + * Give the screen package a chance to cleanup + */ + screen_finish(); + +/* + * Can't restore the terminal modes if they haven't been saved yet + */ + if(tty_modes_saved != YES) return; + +/* + * Restore the modes that we saved away previously + */ +#if HAVE_TERMIOS_H + if(tcsetattr(tty_input_chan,TCSADRAIN,&tty_input_modes) < 0){ + perror("could not set input tty modes"); + punt(errno); + }/* End IF */ + if(tcsetattr(tty_output_chan,TCSADRAIN,&tty_output_modes) < 0){ + perror("could not set output tty modes"); + punt(errno); + }/* End IF */ +#else +#ifdef UNIX + if(ioctl(tty_input_chan,SET_IOCTL,&tty_input_modes) < 0){ + perror("could not restore input tty modes"); + }/* End IF */ + + if(ioctl(tty_output_chan,SET_IOCTL,&tty_output_modes) < 0){ + perror("could not restore output tty modes"); + }/* End IF */ +#endif +#endif + +}/* End Routine */ + + + +/* TTY_INPUT_PENDING - Check if there is input waiting on the cmd channel + * + * Function: + * + * This routine returns a true false indication of whether or not there + * is input waiting on the command channel. This generally gets used to + * set the input_pending_flag variable. Now it is set to only work if + * the baud rate is quite low, since some of the current side effects are + * undesirable. + */ +int +tty_input_pending() +{ +#ifdef HAS_FIONREAD +long temp; +#endif + +#ifdef VMS +struct sense_typeahead_buffer { + unsigned short count; + unsigned char first_char; + unsigned char reserved1; + unsigned long reserved2; +} sense_typeahead_buffer; +#endif + +#ifdef SEQUOIA +struct sequoia_weirdo_get_param_buffer { + unsigned short fc_open_mode; + unsigned long fc_num_bytes_remaining; + char fc_no_delay; + char fc_modify_no_delay; + char fc_append; + char fc_modify_append; + char fc_close_across_exec; + char fc_modify_close_across_exec; + char fc_no_further_opens; + char fc_modify_no_further_opens; +}term_params; + +typedef enum +{ + FC_SET_PARAMETERS, + FC_GET_PARAMETERS, + FC_SET_BUFFER_REPLACEMENT_PARAMETERS, + FC_GET_BUFFER_REPLACEMENT_PARAMETERS, +} FILE_CONTROL_REQUEST; +#endif + +#ifdef SEQUOIA +int err; + err = seqksr_file_control(&term_params,tty_input_chan,FC_GET_PARAMETERS); + if(err == 0 && term_params.fc_num_bytes_remaining != 0) return(YES); +#endif + +#ifdef HAS_POLL + int err; + struct pollfd pollStructs[1]; +#endif + +#ifdef HAS_SELECT + int err; + fd_set theFds; + struct timeval zeroTime; +#endif + + PREAMBLE(); + +#ifdef HAS_SELECT + FD_ZERO( &theFds ); + FD_SET( tty_input_chan, &theFds ); + zeroTime.tv_sec = 0; + zeroTime.tv_usec = 0; + err = select( tty_input_chan+1, &theFds, 0, 0, &zeroTime ); + if(err > 0) + { + return(YES); + } +#endif + +#ifdef HAS_POLL + pollStructs[0].fd = tty_input_chan; + pollStructs[0].events = POLLIN; + pollStructs[0].revents = 0; + err = poll( pollStructs, ELEMENTS(pollStructs), 0 ); + if( err >= 0 && pollStructs[0].revents & POLLIN) return(YES); +#endif + +#ifdef HAS_FIONREAD + temp = 0; + if(ioctl(tty_input_chan,FIONREAD,&temp) < 0) return(NO); + if(temp > 0) return(YES); +#endif + +#ifdef VMS + temp = sys$qiow(0,tty_input_chan,IO$_SENSEMODE|IO$M_TYPEAHDCNT, + 0,0,0,&sense_typeahead_buffer,sizeof(sense_typeahead_buffer),0,0,0,0); + if(!(temp & STS$M_SUCCESS)){ + return(NO); + }/* End IF */ + + if(sense_typeahead_buffer.count > 0) return(YES); + +#endif + + return(NO); + +}/* End Routine */ + + + +/* INIT_SIGNALS - Initialize the signal interface for our use + * + * Function: + * + * This routine sets up the way that we want signals to behave + */ +void +init_signals() +{ + +#if 0 +int cmd_suspend(); +#endif +int cmd_interrupt(); +int cmd_alarm(); +int cmd_winch(); + + PREAMBLE(); + +#ifdef CHECKPOINT + signal(SIGALRM,(void (*)())cmd_alarm); + alarm(checkpoint_interval); +#endif /* CHECKPOINT */ + + signal(SIGINT,(void (*)())cmd_interrupt); + +#ifdef JOB_CONTROL + if(suspend_is_okay_flag == YES){ + signal(SIGTSTP,(void (*)())cmd_suspend); + } +#endif + +#ifdef SUN_STYLE_WINDOW_SIZING + signal(SIGWINCH,(void (*)())cmd_winch); +#endif + +}/* End Routine */ + + + +/* TECO_INI - Routine to read the TECO_INI file + * + * Function: + * + * This routine reads the user's TECO_INI file and sets up the default + * Q-Register contents he has specified. It also executes Q-Register 0. + */ +void +teco_ini() +{ +register char *cp; +register int c,dc; +register int i; +char filename[LINE_BUFFER_SIZE]; +char tmp_message[LINE_BUFFER_SIZE]; +FILE *fp; +struct buff_header *hbp; +char zero_flag = NO; +char comment_flag = NO; + + PREAMBLE(); + +#ifdef UNIX + + if((cp = getenv("HOME"))){ + (void) strcpy(filename,cp); + }/* End IF */ + + else return; + + (void) strcat(filename,"/.teco_ini"); + +#endif + +#ifdef VMS + + strcpy(filename,"SYS$LOGIN:TECO.INI"); + +#endif + + fp = fopen(filename,"r"); + + if(fp == NULL) return; + +/* + * Outer loop reads the name of the Q-Register to be loaded and the delimiter, + * inner loop reads contents of a Q-Register in. + */ + while(1){ + while(1){ + if((c = getc(fp)) == EOF){ + fclose(fp); + if(zero_flag) exec_doq0(); + return; + }/* End IF */ + + if(comment_flag){ + if(c == '!') comment_flag = NO; + continue; + }/* End IF */ + + if(c == '!'){ + comment_flag = YES; + continue; + }/* End IF */ + + if(isspace(c)) continue; + break; + + }/* End While */ + + if(c == '0') zero_flag = YES; + + hbp = buff_qfind(c,1); + if(hbp == NULL){ + fclose(fp); + return; + }/* End IF */ + + if((dc = getc(fp)) == EOF){ + sprintf(tmp_message,"?EOF while loading Q-REGISTER <%c>",c); + error_message(tmp_message); + fclose(fp); + return; + }/* End IF */ + + while(1){ + if((i = getc(fp)) == EOF){ + sprintf(tmp_message,"?EOF while loading Q-REGISTER <%c>",c); + error_message(tmp_message); + fclose(fp); + return; + }/* End IF */ + + if(i == dc) break; + + buff_insert_char(hbp,hbp->zee,i); + + }/* End While */ + + hbp->ismodified = NO; + + }/* End While */ + +}/* End Routine */ + + + +void +check_for_forced_screen_size(argc,argv) +int argc; +char **argv; +{ +register int i; +char *cp, c; + + PREAMBLE(); + + for(i = 1; i < argc; i++){ + if(argv[i][0] == '-'){ + cp = &argv[i][1]; + while((c = *cp++)){ + switch(c){ + case 'X': case 'x': + case 'W': case 'w': + if(isdigit((int)*cp)){ + forced_width = 0; + while(isdigit((int)*cp)){ + forced_width = forced_width * 10 + *cp++ - '0'; + }/* End While */ + }/* End IF */ + else if( ((i+1) < argc) && isdigit((int)argv[i+1][0])){ + forced_width = atoi(argv[i+1]); + i++; + } + else forced_width = 80; + break; + case 'Y': case 'y': + case 'H': case 'h': + if(isdigit((int)*cp)){ + forced_height = 0; + while(isdigit((int)*cp)){ + forced_height = forced_height * 10 + + *cp++ - '0'; + }/* End While */ + }/* End IF */ + else if( ((i+1) < argc) && isdigit((int)argv[i+1][0])){ + forced_height = atoi(argv[i+1]); + i++; + } + else forced_height = 24; + break; + case 'O': case 'o': + output_tty_name = argv[i+1]; + i++; + break; + }/* End Switch */ + }/* End While */ + continue; + }/* End IF */ + + }/* End FOR */ + +}/* End Routine */ + + + +/* HANDLE_COMMAND_LINE - OS Dependent Code to handle command line arguments + * + * Function: + * + * This code handles the command line arguments to VTECO. This requires + * different processing for different operating systems. + */ +#ifdef UNIX +int +handle_command_line(which_time,argc,argv) +int which_time; +int argc; +char **argv; +{ +register int i; +extern struct buff_header *curbuf; +int first_file_argument; +char *cp, c; +int arg_skip; + + PREAMBLE(); + + first_file_argument = 0; + for(i = 1; i < argc; i += arg_skip){ + arg_skip = 1; + if(argv[i][0] == '-'){ + cp = &argv[i][1]; + while((c = *cp++)){ + switch(c){ + case 'C': case 'c': + checkpoint_enabled = NO; + break; + case 'X': case 'x': + case 'W': case 'w': + if(isdigit((int)*cp)){ + forced_width = 0; + while(isdigit((int)*cp)){ + forced_width = forced_width * 10 + *cp++ - '0'; + }/* End While */ + }/* End IF */ + else if( ((i+1) < argc) && isdigit((int)argv[i+1][0])){ + forced_width = atoi(argv[i+1]); + arg_skip++; + } + else forced_width = 80; + break; + case 'Y': case 'y': + case 'H': case 'h': + if(isdigit((int)*cp)){ + forced_height = 0; + while(isdigit((int)*cp)){ + forced_height = forced_height * 10 + + *cp++ - '0'; + }/* End While */ + }/* End IF */ + else if( ((i+1) < argc) && isdigit((int)argv[i+1][0])){ + forced_height = atoi(argv[i+1]); + i++; + } + else forced_height = 24; + break; + case 'O': case 'o': + output_tty_name = argv[i+1]; + arg_skip++; + break; + default: + fprintf(stderr,"teco: unknown switch '%c'\n",c); + cp = ""; + break; + }/* End Switch */ + }/* End While */ + continue; + }/* End IF */ + + if(which_time == 0) return( SUCCESS ); + + if(!first_file_argument) first_file_argument = i; + if(!outof_memory){ + buff_openbuffer(argv[i],0,readonly_flag); + }/* End IF */ + + }/* End FOR */ + + if(first_file_argument){ + buff_openbuffer(argv[first_file_argument],0,readonly_flag); + }/* End IF */ + + if(piped_input_flag){ + buff_openbuffnum(-1,0); + buff_readfd(curbuf,"Standard Input",fileno(stdin)); + curbuf->dot = 0; + }/* End IF */ + + return( SUCCESS ); + +}/* End Routine */ +#endif + +#ifdef VMS + +int +handle_command_line(which_time,argc,argv) +int which_time; +int argc; +char **argv; +{ +register int i,j; +int count; +register char *cp,*dp; +struct wildcard_expansion *name_list,*np; +struct wildcard_expansion *expand_filename(); +int number_of_buffers_opened = 0; +char switch_buffer[TECO_FILENAME_TOTAL_LENGTH]; +char *command_line; +struct dsc$descriptor command_desc; +struct dsc$descriptor symbol_value; +$DESCRIPTOR (symbol_name,"teco_spawn_execute"); +long table = LIB$K_CLI_LOCAL_SYM; +long flags = 0; +long status; +long completion_status; +long sub_pid; + + PREAMBLE(); + + for(i = 1; i < argc; i++){ + cp = argv[i]; + if(*cp == '/'){ + cp++; + dp = switch_buffer; + while(isalnum(*cp)) *dp++ = *cp++; + *dp = '\0'; + if(strcmp(switch_buffer,"subjb") == 0){ + continue; + }/* End IF */ + + else if(strcmp(switch_buffer,"nosuspend") == 0){ + suspend_is_okay_flag = NO; + }/* End IF */ + + else if(strcmp(switch_buffer,"nocheckpoint") == 0){ + checkpoint_enabled = NO; + }/* End IF */ + + else if(strcmp(switch_buffer,"spawn") == 0){ + if(which_time == 1){ + cp -= 5; + dp = "subjb"; + while(*dp) *cp++ = *dp++; + strcpy(switch_buffer,"$ "); + strcat(switch_buffer,argv[0]); + symbol_value.dsc$w_length = strlen(switch_buffer); + symbol_value.dsc$b_dtype = 0; + symbol_value.dsc$b_class = DSC$K_CLASS_S; + symbol_value.dsc$a_pointer = switch_buffer; + status = lib$set_symbol(&symbol_name,&symbol_value,&table); + if(status != SS$_NORMAL){ + perror("could not create symbol 'teco_spawn_execute'"); + punt(status); + }/* End IF */ + count = strlen(symbol_name.dsc$a_pointer); + for(j = 1; j < argc; j++){ + count += strlen(argv[j]) + 1; + }/* End FOR */ + command_line = tec_alloc(TYPE_C_CBUFF,count+8); + if(command_line == NULL) return(FAIL); + dp = command_line; + cp = symbol_name.dsc$a_pointer; + while(*dp++ = *cp++); dp--; + for(j = 1; j < argc; j++){ + *dp++ = ' '; + cp = argv[j]; + while(*dp++ = *cp++); + dp--; + }/* End FOR */ + command_desc.dsc$w_length = strlen(command_line); + command_desc.dsc$b_dtype = 0; + command_desc.dsc$b_class = DSC$K_CLASS_S; + command_desc.dsc$a_pointer = command_line; + + printf("VTECO: Spawning subjob for edit\n"); + + status = lib$spawn(&command_desc,0,0,&flags,0, + &sub_pid,&completion_status,0,0,0,0,0); + if(status != SS$_NORMAL){ + perror("VTECO: LIB$SPAWN failed"); + punt(status); + }/* End IF */ + + exit(SS$_NORMAL); + + }/* End IF */ + }/* End IF */ + + else { + char tmp_message[LINE_BUFFER_SIZE]; + sprintf( + tmp_message, + "?Unknown VTECO switch '%s'", + switch_buffer + ); + tec_error(EINVAL,tmp_message); + }/* End Else */ + + }/* End IF */ + + if(which_time == 0) return(SUCCESS); + + if(argv[i][0] != '/'){ + np = expand_filename(argv[i]); + while(np){ + number_of_buffers_opened++; + buff_openbuffer(np->we_name,0,readonly_flag); + name_list = np; + np = np->we_next; + tec_release(TYPE_C_WILD,name_list); + }/* End While */ + }/* End IF */ + + }/* End FOR */ + + if(number_of_buffers_opened > 0) buff_openbuffer(0,1,readonly_flag); + + return(SUCCESS); + +}/* End Routine */ + + + +/* EXPAND_FILENAME - Expand VMS wildcards to a list of files + * + * Function: + * + * This routine returns a linked list of expanded filenames. + */ +struct wildcard_expansion * +expand_filename(wildcard_string) +char *wildcard_string; +{ +struct wildcard_expansion *name_list,*np; +struct dsc$descriptor argument; +struct dsc$descriptor result; +struct { + unsigned short curlen; + char body[TECO_FILENAME_TOTAL_LENGTH]; +}result_buffer; +int context; +int fnd_flags; +int status; +char flags = 0; +char *cp,*sp; + +#define NODE_SEEN (1<<0) +#define DEV_SEEN (1<<1) +#define DIR_SEEN (1<<2) +#define VER_SEEN (1<<3) + + PREAMBLE(); + +/* + * Determine which fields are present in the supplied string so that we + * may trim the resulting filespecs some. + */ + for(cp = wildcard_string; *cp != '\0'; cp++){ + switch(*cp){ + case ':': + if(cp[1] == ':'){ + flags |= NODE_SEEN; + cp++; + }/* End IF */ + else flags |= DEV_SEEN | DIR_SEEN; + break; + case '[': + flags |= DIR_SEEN; + break; + case ';': + if(cp[1] != '\0') flags |= VER_SEEN; + break; + }/* End Switch */ + }/* End FOR */ + + argument.dsc$w_length = strlen(wildcard_string); + argument.dsc$b_dtype = 0; + argument.dsc$b_class = DSC$K_CLASS_S; + argument.dsc$a_pointer = wildcard_string; + + result.dsc$w_length = sizeof(result_buffer.body); + result.dsc$b_dtype = DSC$K_DTYPE_VT; + result.dsc$b_class = DSC$K_CLASS_VS; + result.dsc$a_pointer = &result_buffer; + + context = 0; + fnd_flags = (1<<1); + + name_list = NULL; + + while(1){ + status = lib$find_file(&argument,&result,&context,0,0,0,&fnd_flags); + if((status & 0xFFFF) != SS$_NORMAL) break; + np = tec_alloc(TYPE_C_WILD, sizeof(struct wildcard_expansion)); + if(np == NULL) return(NULL); + +/* + * Trim the resulting filespec as much as possible + */ + sp = result_buffer.body; + sp[result_buffer.curlen] = '\0'; + + if((flags & DEV_SEEN) == 0){ + cp = sp; + while(*cp && *cp != ':') cp++; + if(*cp == ':') strcpy(sp,cp + 1); + }/* End IF */ + + if((flags & DIR_SEEN) == 0){ + cp = sp; + while(*cp && *cp != '[') cp++; + if(*cp == '['){ + sp = cp + 1; + while(*sp && *sp != ']') sp++; + if(*sp == ']'){ + strcpy(cp,sp+1); + }/* End IF */ + sp = result_buffer.body; + }/* End IF */ + }/* End IF */ + + if((flags & VER_SEEN) == 0){ + cp = sp; + while(*cp && *cp != ';') cp++; + *cp = '\0'; + }/* End IF */ + + if(strlen(sp) > (sizeof(np->we_name)-1)){ + error_message("Filename too long"); + continue; + }/* End IF */ + + strcpy(np->we_name,sp); + np->we_next = name_list; + name_list = np; + }/* End While */ + + lib$find_file_end(&context); + +/* + * If no expansion names at all, perhaps he specified a non-wild file which + * he wants to create. If so, we will just use the wild specification as is. + */ + if(name_list == NULL){ + np = tec_alloc(TYPE_C_WILD, sizeof(struct wildcard_expansion)); + if(np == NULL) return(NULL); + if(strlen(wildcard_string) > (sizeof(np->we_name)-1)){ + error_message("Filename too long"); + return(NULL); + }/* End IF */ + + strcpy(np->we_name,wildcard_string); + np->we_next = name_list; + name_list = np; + + }/* End IF */ + + return(name_list); + +}/* End Routine */ + +/* END OF VMS CONDITIONAL CODE */ + +#endif + + + +/* + * EXPAND_FILENAME - Expand a wildcard specification to a list of files + * + * Function: + * + * This routine returns a linked list of expanded filenames. + * + */ +#ifdef UNIX +struct wildcard_expansion *name_list; +struct wildcard_expansion *name_list_end; + +struct wildcard_expansion * +expand_filename(wildcard_string) +char *wildcard_string; +{ +char temp_name[TECO_FILENAME_TOTAL_LENGTH]; +register char *cp,*sp; +struct passwd *pw; +struct passwd *getpwnam(); + + PREAMBLE(); + + name_list = NULL; + name_list_end = NULL; + +/* + * Else, start branching down into the directory list he specified + */ + if(wildcard_string[0] == '/'){ + process_directory(&wildcard_string[0],"/",2); + } + else if (wildcard_string[0] == '~'){ + cp = temp_name; + sp = &wildcard_string[1]; + while(*sp && *sp != '/') *cp++ = *sp++; + *cp++ = '\0'; + if(temp_name[0] == '\0'){ + if((cp = getenv("HOME"))) strcpy(temp_name,cp); + } + else { + pw = getpwnam(temp_name); + if(pw) strcpy(temp_name,pw->pw_dir); + } + strcat(temp_name,sp); + process_directory(temp_name,"/",2); + } + else { + strcpy(temp_name,"./"); + strcat(temp_name,wildcard_string); + process_directory(temp_name,".",1); + } + + return(name_list); + +}/* End Routine */ + +void +process_directory(wildstr,path,flags) +char *wildstr; +char *path; +int flags; +{ +char directory_path[TECO_FILENAME_TOTAL_LENGTH]; +struct wildcard_expansion *np; +DIR *dirp; + +struct dirent *dp; + +struct stat statbuf; +int c; +char *cp; + + PREAMBLE(); + +/* + * Find the next slash, and if there isn't one, then we have reached + * the end of the file specification, and this should be a file rather + * than a directory. + */ + wildstr = (char *)strchr(wildstr,'/'); + + directory_path[0] = '\0'; + + if(wildstr == NULL){ + if(flags < 0){ + if(stat(path,&statbuf) < 0) return; +#ifndef USE_S_MACS + if((statbuf.st_mode & S_IFMT) != S_IFREG) return; +#else + if(!S_ISREG(statbuf.st_mode)) return; +#endif + }/* End IF */ + if(strlen(path) > (sizeof(np->we_name)-1)){ + error_message("Filename too long"); + return; + }/* End IF */ + np = (struct wildcard_expansion *) + tec_alloc(TYPE_C_WILD, sizeof(struct wildcard_expansion)); + if(np == NULL) return; + np->we_next = NULL; + strcpy(np->we_name,path); + if(name_list == NULL) name_list = np; + if(name_list_end)name_list_end->we_next = np; + name_list_end = np; + return; + }/* End IF */ + +#ifndef USE_S_MACS + if(stat(path,&statbuf) < 0 || ((statbuf.st_mode & S_IFMT) != S_IFDIR)){ + return; + }/* End IF */ +#else + if (stat(path,&statbuf) < 0 || !S_ISDIR(statbuf.st_mode)) { + return; + } +#endif + +/* + * This loop tests whether there are any wildcard characters in the + * next directory. If not, don't go through the overhead of reading + * directories, just simply hop down to that subdir. + */ + cp = wildstr+1; + while((c = *cp++)){ + switch(c){ + case '*': + case '?': + case '[': + case '{': + while(*cp != '\0' && *cp != '/') cp++; + goto read_directory; + case '\0': + strcpy(directory_path,path); + strcat(directory_path,"/"); + strcat(directory_path,wildstr+1); + process_directory(NULL,directory_path,-1); + return; + case '/': + cp--; + *cp = '\0'; + switch(flags){ + case 2: + strcpy(directory_path,"/"); + strcat(directory_path,wildstr+1); + flags = 0; + break; + case 1: + strcpy(directory_path,wildstr+1); + flags = 0; + break; + case 0: + case -1: + strcpy(directory_path,path); + strcat(directory_path,"/"); + strcat(directory_path,wildstr+1); + break; + }/* End Switch */ + + *cp = '/'; + process_directory(wildstr+1,directory_path,flags); + return; + }/* End Switch */ + }/* End While */ + + switch(flags){ + case 2: + strcat(directory_path,wildstr); + flags = 0; + break; + case 1: + strcpy(directory_path,wildstr+1); + flags = 0; + break; + case 0: + case -1: + strcpy(directory_path,path); + strcat(directory_path,wildstr); + break; + }/* End Switch */ + process_directory("",directory_path,flags); + return; + +read_directory: + + dirp = opendir(path); + + while((dp = readdir(dirp)) != NULL) { +#ifndef _POSIX_SOURCE + if(dp->d_ino == 0) continue; +#endif /* _POSIX_SOURCE */ + if(match_name(dp->d_name,wildstr+1)){ + switch(flags){ + case 2: + strcpy(directory_path,"/"); + strcat(directory_path,dp->d_name); + break; + case 1: + strcpy(directory_path,dp->d_name); + break; + case -1: + case 0: + strcpy(directory_path,path); + strcat(directory_path,"/"); + strcat(directory_path,dp->d_name); + }/* End Switch */ + process_directory(cp,directory_path,-1); + }/* End IF */ + }/* End While */ + + closedir(dirp); + +}/* End Routine */ + +/* + * MATCH_NAME - Check whether a name satisfies a wildcard specification + * + * Function: + * + * This routine attempts to do csh style wildcard matching so that + * internal teco routines may support this behavior. + */ +int +match_name(name,pattern) +char *name; +char *pattern; +{ +char temp_buff[TECO_FILENAME_TOTAL_LENGTH]; +register int c; +register char *cp,*sp; +int match; +int previous_char; +int pattern_char; + + PREAMBLE(); + + while(1){ + switch(pattern_char = *pattern++){ + case '/': + case '\0': + return(*name == '\0' ? 1 : 0); + case '?': + return(*name == '\0' || *name == '/' ? 0 : 1); +/* + * Open-Bracket allows any of a list of characters to match, such as [abc], + * or a range such as [a-c], or a combination such as [abg-mz]. + */ + case '[': + match = 0; + c = *name; + previous_char = 'A'; + while((pattern_char = *pattern++)){ + if(pattern_char == c) match = 1; + if(pattern_char == ']'){ + if(match) break; + return(0); + }/* End IF */ + if(pattern_char == '-'){ + pattern_char = *pattern++; + if(pattern_char == ']'){ + pattern--; + pattern_char = 'z'; + }/* End IF */ + if(c >= previous_char && c <= pattern_char){ + match = 1; + }/* End IF */ + }/* End IF */ + previous_char = pattern_char; + }/* End While */ + break; +/* + * Brace allows a list of strings, any one of which may match, and any + * one of which may contain further wildcard specifications. + */ + case '{': + cp = temp_buff; + while((pattern_char = *pattern++)){ + if(pattern_char == ',' || pattern_char == '}'){ + sp = pattern; + if(pattern_char == ','){ + while((c = *sp++)){ + if(c == '}') break; + }/* End While */ + }/* End IF */ + while((*cp++ = *sp++)); + if(match_name(name,temp_buff)) return(1); + if(pattern_char == '}') return(0); + cp = temp_buff; + }/* End IF */ + else *cp++ = pattern_char; + }/* End While */ + break; +/* + * Asterisk matches any string + */ + case '*': + if(*pattern == '\0') return(1); + if(*pattern == '/') return(1); + for(c = 0; name[c] != '\0'; c++){ + if(match_name(&name[c],pattern)) return(1); + }/* End FOR */ + return(0); + default: + if(pattern_char != *name) return(0); + break; + }/* End Switch */ + name++; + }/* End While */ +}/* End Routine */ + +#endif + + + +/* PUNT - Exit with a specified error code + * + * Function: + * + * PUNT is called with an errno code with which we want to exit + * + */ +void +punt(exit_code) +int exit_code; +{ + PREAMBLE(); + + exit(exit_code); + +}/* End Routine */ + + +/* TEC_PANIC - Print an error string + * + * Function: + * + * This routine is called previous to punt to print an error string + */ +void +tec_panic(string) +char *string; +{ + PREAMBLE(); + + fputs(string,stdout); + fputs("\n",stdout); + +#ifndef UNIX + exit(1); +#else + kill(getpid(),SIGQUIT); +#endif + +}/* End Routine */ + +/* TEC_ERROR - Exit with specified error code, printing an error string + * + * Function: + * + * This routine is called to exit with the specified error code + * after printing the supplied error string. + */ +void +tec_error(code,string) +int code; +char *string; +{ + PREAMBLE(); + + fprintf(stderr,"%s\n",string); + +#ifdef VMS + exit(1); +#endif + + exit(code); + +}/* End Routine */ + +#if DEBUG_UNUSED +/* OPEN_DEBUG_LOG_FILE - Open a file to write debug output to + * + * Function: + * + * This routine is called so that a log file is open for writing of + * debugging messages. + */ +void +open_debug_log_file() +{ + PREAMBLE(); + + teco_debug_log = fopen("teco.dbg","w"); + if(teco_debug_log == NULL){ + teco_debug_log = stderr; + }/* End IF */ + +}/* End Routine */ +#endif + + + +/* MAP_BAUD - Map system dependent baud fields to a standard integer + * + * Function: + * + * This routine takes an operating system specific baud rate + * representation, and maps it into a simple integer value. + */ +int +map_baud(input_baud_rate) +int input_baud_rate; +{ +register int i; +int return_baud; + +#ifdef UNIX + +#ifndef B19200 +#define B19200 B9600 +#endif +#ifndef B38400 +#define B38400 B9600 +#endif + +static unsigned int encoded_bauds[] = { + B0,B50,B75,B110,B134,B150,B200,B300,B600,B1200, + B1800,B2400,B4800,B9600,B19200,B38400, + EXTA,EXTB }; +static int equivalent_baudrates[] = { + 0,50,75,110,134,150,200,300,600,1200, + 1800,2400,4800,9600,19200,38400, + 9600,9600 }; + +/* END OF UNIX CONDITIONAL CODE */ + +#endif + +#ifdef VMS +static unsigned char encoded_bauds[] = { + TT$C_BAUD_50,TT$C_BAUD_75,TT$C_BAUD_110,TT$C_BAUD_134, + TT$C_BAUD_150,TT$C_BAUD_300,TT$C_BAUD_600,TT$C_BAUD_1200, + TT$C_BAUD_1800,TT$C_BAUD_2000,TT$C_BAUD_2400,TT$C_BAUD_3600, + TT$C_BAUD_4800,TT$C_BAUD_7200,TT$C_BAUD_9600,TT$C_BAUD_19200 }; +static int equivalent_baudrates[] = { + 50,75,110,134, + 150,300,600,1200, + 1800,2000,2400,3600, + 4800,7200,9600,19200 }; + +/* END OF VMS CONDITIONAL CODE */ + +#endif + + PREAMBLE(); + +/* + * Default is a common baud rate incase we miss somehow + */ + return_baud = 9600; + +/* + * Determine how many entries there are in the baud table + */ + i = sizeof(encoded_bauds) / sizeof(encoded_bauds[0]); + +/* + * Now scan the table for our input baud rate + */ + while(--i >= 0){ + if(input_baud_rate != encoded_bauds[i]) continue; + return_baud = equivalent_baudrates[i]; + break; + }/* End While */ + + return(return_baud); + +}/* End Routine */ + + + +/* ERROR_TEXT - Return 'errno' style error string + * + * Function: + * + * This routine is called to return the error string associated with an + * errno error code. + */ +char * +error_text(err_num) +register int err_num; +{ + +#ifdef VMS + + int sys_nerr; + +static char *tec_errlist[] = { + "success", /* ESUCCESS */ + "not owner", /* EPERM */ + "no such file or directory", /* ENOENT */ + "no such process", /* ESRCH */ + "interrupted system call", /* EINTR */ + "i/o error", /* EIO */ + "no such device or address", /* ENXIO */ + "arg list too long", /* E2BIG */ + "exec format error", /* ENOEXEC */ + "bad file number", /* EBADF */ + "no children", /* ECHILD */ + "no more processes", /* EAGAIN */ + "not enough core", /* ENOMEM */ + "permission denied", /* EACCES */ + "bad address", /* EFAULT */ + "block device required", /* ENOTBLK */ + "mount device busy", /* EBUSY */ + "file exists", /* EEXIST */ + "cross-device link", /* EXDEV */ + "no such device", /* ENODEV */ + "not a directory", /* ENOTDIR */ + "is a directory", /* EISDIR */ + "invalid argument", /* EINVAL */ + "file table overflow", /* ENFILE */ + "too many open files", /* EMFILE */ + "not a typewriter", /* ENOTTY */ + "text file busy", /* ETXTBSY */ + "file too large", /* EFBIG */ + "no space left on device", /* ENOSPC */ + "illegal seek", /* ESPIPE */ + "read-only file system", /* EROFS */ + "too many links", /* EMLINK */ + "broken pipe", /* EPIPE */ + "math argument", /* EDOM */ + "result too large" /* ERANGE */ +}; + + PREAMBLE(); + + sys_nerr = sizeof(tec_errlist) / sizeof(tec_errlist[0]) - 1; + +/* END OF VMS CONDITIONAL CODE */ + +#endif + +#if 0 + if(err_num < 0 || err_num > sys_nerr){ + return("unknown error"); + }/* End IF */ +#endif + +#ifdef UNIX +#if 0 + return((char *)sys_errlist[err_num]); +#endif + return( strerror( err_num )); +#endif + +#ifdef VMS + return(tec_errlist[err_num]); +#endif + +}/* End Routine */ diff --git a/teco.h b/teco.h new file mode 100644 index 0000000..e1e11b8 --- /dev/null +++ b/teco.h @@ -0,0 +1,657 @@ +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/teco.h,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* teco.h + * Global TECO Definitions + * %W% (PC) %G% + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#define YES 1 +#define NO 0 +#define FAIL 0 +#define SUCCESS 1 +#define NULL 0 + +#define CREATE_OLD_FILES + +#define BLOCKED 2 +#define INVALIDATE 3 + +#define VMAJOR 6 +#define VMINOR 7 +#define AUTO_DATE "$Date: 2007/12/10 21:59:20 $" + +#define TECO_FILENAME_TOTAL_LENGTH 1024 +#define TECO_FILENAME_COMPONENT_LENGTH 256 +#define TECO_READONLY_NAME "visit" +#define TECO_INTERNAL_BUFFER_NAME "TECO-" +#define TERMCAP_BUFFER_SIZE 2048 +#define SCREEN_OUTPUT_BUFFER_SIZE 4096 +#define SCREEN_RESERVED_LINES 2 +#define SCREEN_MAX_LABEL_FIELDS 4 +#define SCREEN_NOMINAL_LINE_WIDTH 132 +#define SCREEN_MAX_LINES 1024 +#define SCREEN_MINIMUM_WINDOW_HEIGHT 4 +#define IO_BUFFER_SIZE 512 +#define LINE_BUFFER_SIZE 1024 +#define DEFAULT_CHECKPOINT (5*60) +#define TAG_HASH_ENTRIES 1024 +#define PARSER_STRING_MAX 1024 +#define SEARCH_STRING_MAX PARSER_STRING_MAX +#define NORMAL_TAB_WIDTH 8 +#define MAX_TAB_WIDTH 16 +#define MAXOF( a,b) (a) > (b) ? (a) : (b) + +#define INITIAL_LINE_BUFFER_SIZE 32 +#define INCREMENTAL_LINE_BUFFER_SIZE 32 +#define MINIMUM_ALLOCATION_BLOCK 32 +#define LOOKASIDE_COUNT 32 +#define LARGEST_LOOKASIDE_BLOCK (LOOKASIDE_COUNT * MINIMUM_ALLOCATION_BLOCK) +#define BIG_MALLOC_HUNK_SIZE (LARGEST_LOOKASIDE_BLOCK * 64) +#define NORMAL_TAB_WIDTH 8 + +/* + * The following is where the different machines are described so that + * conditional compilation based on different machines quirks can be done. + */ + +/* + * Unless defined otherwise, we assume 32 bits to an integer and 8 bits / char + */ +#ifndef BITS_PER_INT +#define BITS_PER_INT 32 +#endif +#ifndef BITS_PER_CHAR +#define BITS_PER_CHAR 8 +#endif + +#define ESCAPE 27 +#define RUBOUT 127 + +#define CNTRL_A 1 +#define CNTRL_D 4 +#define CNTRL_E 5 +#define CNTRL_G 7 +#define CNTRL_N 14 +#define CNTRL_R 18 +#define CNTRL_U 21 +#define CNTRL_V 22 +#define CNTRL_W 23 +#define CNTRL_X 24 +#define CNTRL_Z 26 +#define BELL 7 + +/* + * Define memory allocation block types + */ +#define TYPE_C_MINTYPE 33 +#define TYPE_C_CBUFF 33 +#define TYPE_C_CPERM 34 +#define TYPE_C_CMD 35 +#define TYPE_C_UNDO 36 +#define TYPE_C_SCR 37 +#define TYPE_C_SCRBUF 38 +#define TYPE_C_SCREEN 39 +#define TYPE_C_SCREENBUF 40 +#define TYPE_C_LINE 41 +#define TYPE_C_LINEBUF 42 +#define TYPE_C_BHDR 43 +#define TYPE_C_WINDOW 44 +#define TYPE_C_LABELFIELD 45 +#define TYPE_C_WILD 46 +#define TYPE_C_TAGS 47 +#define TYPE_C_TAGENT 48 +#define TYPE_C_TAGSTR 49 +#define TYPE_C_MAXTYPE 49 + +#define MAGIC_SCREEN 0x01010101 +#define MAGIC_BUFFER 0x02020202 +#define MAGIC_LINE 0x03030303 +#define MAGIC_LINE_LOOKASIDE 0x04040404 +#define MAGIC_FORMAT 0x05050505 +#define MAGIC_FORMAT_LOOKASIDE 0x06060606 + +#ifdef DEBUG1 +#define PREAMBLE() \ + do_preamble_checks(); + +#define RETURN \ + do_return_checks(); \ + return; + +#define RETURN_VAL(val) \ + do_return_checks(); \ + return(val); +#else +#define PREAMBLE() +#define RETURN +#define RETURN_VAL() +#endif /* DEBUG1 */ + + +#define ELEMENTS(xyzzy) (sizeof(xyzzy)/sizeof(xyzzy[0])) + +/* + * Define structures used throughout TECO + */ + struct position_cache { + struct buff_line *lbp; + int base; + }; + +#define BUFF_CACHE_CONTENTS(pos_cache) \ + (pos_cache)->lbp->buffer[(pos_cache)->base] +#define BUFF_INCREMENT_CACHE_POSITION(pos_cache) \ + if(++((pos_cache)->base) >= (pos_cache)->lbp->byte_count){ \ + (pos_cache)->lbp = (pos_cache)->lbp->next_line; (pos_cache)->base = 0; \ + } +#define BUFF_DECREMENT_CACHE_POSITION(pos_cache) \ + { \ + if((pos_cache)->base == 0){ \ + (pos_cache)->lbp = (pos_cache)->lbp->prev_line; \ + (pos_cache)->base = (pos_cache)->lbp->byte_count; \ + } \ + (pos_cache)->base -= 1; \ + } + + struct buff_header { + int buf_magic; + unsigned int buf_hash; + char *name; + int buffer_number; + struct buff_header *next_header; + char ismodified; + char isreadonly; + char isbackedup; + int dot; + int zee; + int ivalue; + struct position_cache pos_cache; + + struct buff_line *first_line; + }; + + struct buff_line { + int lin_magic; + int buffer_size; + int byte_count; + char *buffer; + struct buff_line *next_line; + struct buff_line *prev_line; + struct format_line *format_line; + }; + + struct format_line { + int fmt_magic; + struct buff_header *fmt_owning_buffer; + struct buff_line *fmt_buffer_line; + int fmt_buffer_size; + short *fmt_buffer; + int fmt_sequence; + struct format_line *fmt_next_line; + struct format_line *fmt_prev_line; + struct format_line *fmt_next_alloc; + struct format_line *fmt_prev_alloc; + struct format_line *fmt_next_window; + struct format_line *fmt_prev_window; + char fmt_in_use; + char fmt_permanent; + struct screen_line *fmt_saved_line; + short fmt_visible_line_position; + struct window *fmt_window_ptr; + }; + + struct screen_line { + int scr_magic; + short *buffer; + struct format_line *companion; + int sequence; + }; + + struct window { + struct window *win_next_window; + int win_window_number; + int win_x_size; + int win_y_size; + int win_y_base; + int win_y_end; + struct buff_header *win_buffer; + struct format_line win_label_line; + struct format_line *win_dot_format_line; + int win_dot_screen_offset; + char *win_label_field_contents[SCREEN_MAX_LABEL_FIELDS]; + }; + + struct search_element { + unsigned char type; + unsigned char value; + union { + int intarray[256/BITS_PER_INT]; + char bytearray[256/BITS_PER_CHAR]; + }bitmask; + union { + int intarray[256/BITS_PER_INT]; + char bytearray[256/BITS_PER_CHAR]; + }repmask; + }; + + struct search_buff { + int length; + char input[PARSER_STRING_MAX]; + struct search_element data[PARSER_STRING_MAX]; + char error_message_given; + }; + + struct wildcard_expansion { + struct wildcard_expansion *we_next; + char we_name[TECO_FILENAME_TOTAL_LENGTH-4]; + }; + + struct tagent { + char *te_symbol; + char *te_filename; + char *te_lineno; + char *te_search_string; + struct tagent *te_next; + }; + + struct tags { + struct tagent *current_entry; + struct tagent *tagents[TAG_HASH_ENTRIES]; + }; + +/* + * Define the various flags in a screen display short int + */ +#define SCREEN_M_DATA 0x00FF +#define SCREEN_M_FLAGS 0xFF00 +#define SCREEN_M_REVERSE 0x0100 + +/* + * Define constants for the label line fields + */ +#define LABEL_C_TECONAME 0 +#define LABEL_C_FILENAME 1 +#define LABEL_C_READONLY 2 +#define LABEL_C_MODIFIED 3 + +/* + * Define constants for EJ command + */ +#define SETOPTION_MIN_OPTION 1 + +#define SETOPTION_ALTERNATE_ESCAPE_CHAR 1 +#define SETOPTION_SCREEN_HEIGHT 2 +#define SETOPTION_SCREEN_WIDTH 3 +#define SETOPTION_ALTERNATE_DELETE_CHAR 4 +#define SETOPTION_FORCE_8_BIT_CHARS 5 +#define SETOPTION_TAB_WIDTH 6 +#define SETOPTION_HIDE_CR_CHARS 7 + +#define SETOPTION_MAX_OPTION 7 + +/* + * Define constants for FT command + */ +#define TAGS_MIN_OPTION 0 + +#define TAGS_LOAD_TAGS_FILE 0 +#define TAGS_SEARCH_TAGS_FILE 1 +#define TAGS_TEST_FOR_LOADED_TAGS 2 +#define TAGS_INSERT_TARGET_FILE 3 +#define TAGS_INSERT_SEARCH_STRING 4 +#define TAGS_INSERT_LINENO 5 +#define TAGS_INSERT_ALL 6 + +#define TAGS_MAX_OPTION 6 + +/* + * Define useful macros + */ +#define UPCASE(char) (islower(char) ? TOUPPER(char) : char) +#ifdef SYS_III_UNIX +#define TOUPPER(char) _toupper(char) +#define TOLOWER(char) _tolower(char) +#else +#define TOUPPER(char) toupper(char) +#define TOLOWER(char) tolower(char) +#endif + +typedef unsigned long teco_ptrint_t; + +/* + * We define unix except for the really different operating systems, like + * vms. It lets us write our own version of functions which simply do not + * exist outside of unix. + */ +#ifndef VMS +#define UNIX +#define JOB_CONTROL +#endif + +/* + * Include Files From GNU Autoconf/Autoheader + */ +#include "config.h" + +#ifdef HAVE_SYS_IOCTL_H +#include +#endif + +/* + * sys/filio.h has the FIONREAD definition + */ +#ifdef HAVE_SYS_FILIO_H +#include +#endif +#ifdef FIONREAD +#define HAS_FIONREAD +#endif + +#if HAVE_SYS_TYPES_H +#include +#if HAVE_SYS_WAIT_H +# include +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +# define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif +#endif + +#if HAVE_SYS_STAT_H +#include +#endif + +#if STDC_HEADERS +#include +#include +#else +#if HAVE_STDLIB_H +#include +#endif +#endif + +#if HAVE_STRING_H +# if !STDC_HEADERS && HAVE_MEMORY_H +# include +# endif +# include +# if !HAVE_STRCHR +# if !defined(strchr) +# define strchr index +# endif +# if !defined(strrchr) +# define strrchr rindex +# endif +# endif +#endif + +/* on SGI this gets us BZERO */ +#if HAVE_STRINGS_H +#include +#endif + +#if HAVE_INTTYPES_H +#include +#else +#if HAVE_STDINT_H +#include +#endif +#endif + +#if HAVE_UNISTD_H +#include +#endif + +#if HAVE_DIRENT_H +#include +#define NAMLEN(dirent) strlen((dirent)->d_name) +#else +#define dirent direct +#define NAMLEN(dirent) (dirent)->d_namlen +#if HAVE_SYS_NDIR_H +#include +#endif +#if HAVE_SYS_DIR_H +#include +#endif +#if HAVE_NDIR_H +#include +#endif +#endif + +#if HAVE_SYS_WAIT_H +# include +#endif +#ifndef WEXITSTATUS +# define WEXITSTATUS(stat_val) ((unsigned)(stat_val) >> 8) +#endif +#ifndef WIFEXITED +# define WIFEXITED(stat_val) (((stat_val) & 255) == 0) +#endif + +#if GWINSZ_IN_SYS_IOCTL +# include +#define SUN_STYLE_WINDOW_SIZING 1 +#else +#if HAVE_TERMIOS_H +# include +#define SUN_STYLE_WINDOW_SIZING 1 +#endif +#endif + +#if HAVE_TERMIO_H +# include +#endif + +#if HAVE_FCNTL_H +#include +#endif + +#if !defined(HAVE_TERMIO_H) && !defined(HAVE_TERMIOS_H) +#if HAVE_SGTTY_H +#include +#endif +#endif + +#if HAVE_SYS_FILE_H +#include +#endif + +#if HAVE_SYS_PARAM_H +#include +#endif + +#if HAVE_SYS_SOCKET_H +#include +#endif + +#if HAVE_CTYPE_H +#include +#endif + +#if HAVE_STDIO_H +#include +#endif + +#if HAVE_ERRNO_H +#include +#endif + +#if HAVE_SIGNAL_H +#include +#endif + +#if HAVE_PWD_H +#include +#endif + +#if HAVE_TERMCAP_H +#include +#endif + +/* current end of autoconf stuff*/ + +#if HAVE_LIBTERMCAP +#define TERMCAP +#else +#if defined(HAVE_LIBTERMINFO) +#define TERMINFO +#endif +#endif + +#if !defined(TERMCAP) && !defined(TERMINFO) +#if defined(HAVE_LIBNCURSES) +#define TERMINFO +#endif +#endif + + +/* we prefer select over poll */ +#if HAVE_SYS_SELECT_H +#include +#define HAS_SELECT +#else +#if HAVE_POLL_H +#include +#define HAS_POLL +#endif +#endif + +/* + * Digital Equpment VMS Operating System + */ +#ifdef VMS +#include "tecvms.h" +#endif + +#ifndef CAUSE_BUS_ERROR +#define CAUSE_BUS_ERROR() \ + { \ + int fake_destination; \ + fake_destination = *((int *)(0x0FFFFFFF)); \ + } +#endif + +/* method declarations */ +void error_message(char * string); +void tec_release(unsigned char type, char *addr); +void tec_panic( char *string); +int buff_switch(struct buff_header *hbp,int map_flag); +int buff_read(struct buff_header *hbp,char *name); +int buff_readfd(struct buff_header *hbp,char *name,int iochan); +int buff_insert(struct buff_header *hbp,int position,char *buffer,int length); +int screen_label_line(struct buff_header *buffer,char *string,int field); +int buff_write(struct buff_header *hbp,int chan,int start,int end); +void tecmem_stats(); +void screen_free_format_lines(struct format_line *sbp); +int buff_delete_char(struct buff_header *hbp,int position); +int compile_search_string(struct search_buff *search_tbl); +int cmd_forward_search(int pos1,int pos2,struct search_buff *search_tbl); +int cmd_reverse_search(int pos1,int pos2,struct search_buff *search_tbl); +int buff_cached_contents(struct buff_header *,int,struct position_cache *); +int buff_contents(struct buff_header *hbp,int position); +void pause_while_in_input_wait(void); +void restore_tty(void); +void initialize_tty(void); +void screen_redraw(void); +void screen_resize(void); +void screen_refresh(void); +void screen_message(char *string); +int tty_input_pending(void); +void screen_format_windows(void); +void load_qname_register(void); +void buff_delete(struct buff_header *,int,int); +void screen_reformat_windows(void); +int tag_calc_hash(char *string); +void tag_free_struct(struct tags *tp); +void tecdebug_check_screen_magic(void); +void tecdebug_check_buffer_magic(void); +void tecdebug_check_line_magic(void); +void tecdebug_check_format_magic(void); +void tecdebug_check_companion_pointers(void); +void tecdebug_check_window_pointers(void); +void term_init(void); +int term_goto( int dest_x,int dest_y); +void term_clrtobot(void); +void parser_reset_echo(void); +void term_finish(void); +void term_standend(void); +void term_clrtoeol(void); +void term_standout(void); +void screen_save_current_message(char *,int); +int window_switch(int window_number); +void screen_delete_window( struct window *old_wptr); +void buff_reopenbuff(struct buff_header *bp); +void buff_free_line_buffer_list(struct buff_line *); +int screen_display_window(struct window *wptr); +int buff_find_offset(struct buff_header *,struct buff_line *,int); +int buff_openbuffnum(int,int); +int parse_illegal_buffer_position(int,int,char *); +int buff_insert_char(struct buff_header *,int,char); +int push_qregister(char); +void buff_bulk_insert(struct buff_header *,int,int,struct buff_line *); +void buff_destroy(struct buff_header *); +int cmd_wordmove(int count); +int cmd_search(int,int,struct search_buff *); +int buff_openbuffer(char *,int,int); +int cmd_write(struct buff_header *,char *); +void screen_scroll(int); +void parser_dump_command_line(struct buff_header *); +int parser_replace_command_line(struct buff_header *); +void tec_gc_lists(void); +void screen_deallocate_format_lookaside_list(); +void buff_deallocate_line_buffer_lookaside_list(); +int buff_init(); +void tec_error(int, char *); +int handle_command_line(int,int,char **); +void tecparse(); +//void term_putc(char); +int term_putc(int); +void term_flush(); +int screen_init(); +void screen_finish(); +int exec_doq0(); +void punt(int); +void screen_reset_message(); +int tecparse_syntax(int); +void screen_echo(char); +void cmd_pause(); +void cmd_suspend(); +int term_putnum(char *,int,int); +int term_scroll_region(int,int); +int init_term_description(); +//int tgetent(char *,char *); +//int tgetnum(char *); +char *tec_alloc(int,int); +void initialize_memory_stats( void ); +struct buff_header *buff_qfind(char,char); +char *error_text(int); +struct buff_line *buff_find_line(struct buff_header *hbp,int); +void term_insert_line(int,int); +void term_delete_line(int,int); +struct buff_header *buff_find(char *); +struct buff_header *buff_duplicate(struct buff_header *); +struct window *screen_split_window(struct window *,int,int); diff --git a/tecparse.c b/tecparse.c new file mode 100644 index 0000000..9682bf0 --- /dev/null +++ b/tecparse.c @@ -0,0 +1,2178 @@ +char *tecparse_c_version = "tecparse.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecparse.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecparse.c + * Subroutines to implement the finite state parser + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + struct cmd_token *cmd_list = NULL; + struct cmd_token *last_cmd_list = NULL; + struct cmd_token *jump_to_token; + struct cmd_token *resume_execute_ct; + struct cmd_token *last_token_executed; + struct search_buff search_string; + char user_message[PARSER_STRING_MAX]; + int remembered_dot; + char immediate_execute_flag = YES; + char trace_mode_flag = NO; + + extern int tty_input_chan; + extern int errno; + extern char exit_flag; + extern char intr_flag; + extern char susp_flag; + extern char input_pending_flag; + extern char alternate_escape_character; + extern char main_delete_character; + extern char alternate_delete_character; + extern char checkpoint_flag; + extern char checkpoint_enabled; + extern char waiting_for_input_flag; + extern char resize_flag; + extern char ring_audible_bell; + extern char suspend_is_okay_flag; + extern struct buff_header *curbuf,*buffer_headers; + extern int term_speed; + +/* + * Forward declarations + */ + struct cmd_token *parse_rubout_character(struct cmd_token *,int); + struct cmd_token *parse_rubout_cmd_token(struct cmd_token *); + void preserve_rubout_char(char); + void parser_clean_preserve_list(void); + int parser_getc(void); + int unpreserve_rubout_char(struct cmd_token *); + + + +/* TECPARSE - Main entry point of the parser + * + * Function: + * + * This routine reads input characters and builds the token + * list. It also calls the execute states as long as the go + * flag is set. When it hits the final parse state, it then + * executes any states which were not immediately executed. + */ +void +tecparse() +{ +register struct cmd_token *ct; +int status; +int c; +static char no_mem = 0; + + PREAMBLE(); + +/* + * We start our command token list with a special token that simply means that + * this is the begining of the list. We also set the current state to be the + * initial state of the parser. + */ + resume_execute_ct = NULL; + ct = allocate_cmd_token((struct cmd_token *)NULL); + if(ct != NULL){ + no_mem = 0; + ct->opcode = TOK_C_FIRSTTOKEN; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->ctx.go_flag = YES; + if(immediate_execute_flag == NO) ct->ctx.go_flag = NO; + cmd_list = ct; + screen_reset_echo(cmd_list); +/* + * Now we loop until we reach a final state, or something horrible happens. + */ + while(1){ + c = parser_getc(); + status = tecparse_syntax(c); + if(status == FAIL) break; + }/* End While */ + + screen_format_windows(); + + }/* End IF */ + + else { + if(no_mem){ + tec_panic("no memory available"); + }/* End IF */ + no_mem = 1; + error_message(""); + }/* End Else */ + + screen_refresh(); + +/* + * Here to clean up old parse lists. Note that we always clean up the list + * on last_cmd_list, and then move the current command list over there. This + * is so that we always remember one command string back incase the user uses + * the '*' command to save it all to a q-register. Besides unchaining command + * blocks, the cleanup routine follows down undo lists and macro lists to + * reclaim all the memory they are using. + */ + parser_cleanup_ctlist(last_cmd_list); + last_cmd_list = cmd_list; + cmd_list = NULL; + +/* + * Also clean up the rubout Q-register. + */ + parser_clean_preserve_list(); + +/* + * Also, clean up any lookaside lists + */ + tec_gc_lists(); + +}/* End Routine */ + + + +/* TECPARSE_SYNTAX - Here to manipulate the tree according to new input + * + * Function: + * + * This routine is called with an input byte to manipulate the + * parser tree and execute any associated code. + */ +int +tecparse_syntax(c) +int c; +{ +register struct cmd_token *ct; +struct cmd_token trace_ct; +register struct cmd_token *final_token,*last_token; +int status; +char token_used,token_marked; +char old_modified_state; +struct buff_header *old_curbuf; +struct undo_token *ut; + + PREAMBLE(); + +/* + * Find the end of the current command list. + */ + ct = cmd_list; + while(ct->next_token){ + ct = ct->next_token; + }/* End While */ +/* + * parse_special_character tests whether this character is an immediate + * typing control character which should not be sent to the state machine but + * handled locally. This is how we handle things like rubout, rubout word, + * and Control-U. + */ + if(parse_special_character(ct,c)){ + return(1); + }/* End IF */ +/* + * We only echo the character if is is not a special character + */ + screen_echo(c); + if(input_pending_flag == NO){ + screen_format_windows(); + screen_refresh(); + }/* End IF */ +/* + * We set the token_used flag to indicate that we have not used the input + * byte yet. + */ + token_used = token_marked = NO; +/* + * Now we loop until we use the input byte, or reach a terminating state + */ + while(1){ +/* + * If the last state transition used the input byte, we return to our + * caller. + */ + if(token_used == YES){ + return(1); + }/* End IF */ +/* + * Allocate a command token to hold the next state transition in. The routine + * will automatically link it onto the end of the list. + */ + ct = allocate_cmd_token(ct); + if(ct == NULL){ + error_message(""); + return(1); + }/* End IF */ +/* + * Here we check for the special RETURN state. Although all this could be done + * in the state machine itself, this was a good place to consolidate things. + * We pop the state machine stack a level so as to return to the calling state. + * Note that iarg1 and iarg2 are copied from the calling state, so that they + * are preserved. This means that the substate must return it's value through + * tmpval which is not preserved. + */ + if(ct->ctx.state == STATE_C_RETURN){ + ct->ctx.state = ct->ctx.return_state; + ct->ctx.return_state = ct->ctx.caller_token->ctx.return_state; + ct->ctx.iarg1_flag = ct->ctx.caller_token->ctx.iarg1_flag; + ct->ctx.iarg2_flag = ct->ctx.caller_token->ctx.iarg2_flag; + ct->ctx.iarg1 = ct->ctx.caller_token->ctx.iarg1; + ct->ctx.iarg2 = ct->ctx.caller_token->ctx.iarg2; + ct->ctx.caller_token = ct->ctx.caller_token->ctx.caller_token; + }/* End IF */ +/* + * Set up the initial state of the command token. The COPYTOKEN opcode doesn't + * really mean anything except that the opcode hasn't really been set, so it's + * more for debugging purposes than anything else. Notice that since we are + * actually placing an input byte in input_byte, we set the default that the + * state will indeed eat the byte. If this is not the case, it is up to the + * state to clear the flag. + */ + ct->opcode = TOK_C_COPYTOKEN; + ct->flags |= TOK_M_EAT_TOKEN; + ct->input_byte = c; +/* + * If this is the first time through here for this input character, + * mark this is the input character token so that rubout works correctly. + */ + if(token_marked == NO){ + ct->opcode = TOK_C_INPUTCHAR; + token_marked = YES; + }/* End IF */ +/* + * Call the state machine to process this character. Although more command + * tokens may be added onto the list by the state machine, we leave ct where + * it is for the moment so that we can work forward through all the generated + * states. + */ + parse_input_character(ct,ct); +/* + * If the state machine indicates that the input character was eaten, remember + * this fact. + */ + if(ct->flags & TOK_M_EAT_TOKEN) token_used = YES; +/* + * If this character would cause us to transition into an error state, we + * simply don't allow it. We generate a rubout sequence to remove the offending + * character and allow the user to make a decision about what to do next. + */ + if(ct->ctx.state == STATE_C_ERRORSTATE){ + token_used = YES; + while(ct->next_token) ct = ct->next_token; + ct = parse_rubout_character(ct,0); + screen_reset_echo(cmd_list); + return(1); + }/* End IF*/ + +/* + * If ? debug mode is on, record new syntax tokens in the ? buffer + */ + if(trace_mode_flag == YES){ + last_token = ct; + while(last_token){ + trace_mode(TRACE_C_PARSE,last_token,0); + last_token = last_token->next_token; + }/* End While */ + }/* End IF */ + +/* + * Ok, the state transition seems to have gone smoothly, if the go flag is set + * we are in immediate execute mode and can perform the execution phase of the + * state transition. + */ + if(resume_execute_ct != NULL){ + ct = resume_execute_ct; + ct->ctx.go_flag = YES; + resume_execute_ct = NULL; + } + +/* + * We need a place to chain off undo tokens. If we use the current token, + * loops will distribute undo tokens all over the parse tree, with no way + * to tell which order they were created in. So we find the last token, and + * chain everything off of that. That way, if a rubout is hit, all the undo + * tokens will be undone in order back to the last keystroke token. As the + * user types more keystrokes, we keep finding the new end of the command + * chain, and chaining undo tokens off of that. Thus order will be preserved. + */ + final_token = ct; + while (final_token->next_token) final_token = final_token->next_token; + last_token = ct->prev_token; + + while(ct){ +/* + * We still want to copy tmpvals forward from state to state unless the + * parse stage is attempting a store. + */ + if(last_token){ +/* + * Carry the argument values forward to this state. If the last state was a + * RETURN state, then only copy out the tmpval, and fetch the callers iargs. + */ + if((ct->flags & TOK_M_PARSELOAD_IARG1) == 0){ + ct->ctx.iarg1 = last_token->ctx.iarg1; + }/* End IF */ + if((ct->flags & TOK_M_PARSELOAD_IARG2) == 0){ + ct->ctx.iarg2 = last_token->ctx.iarg2; + }/* End IF */ + + if(ct->ctx.state == STATE_C_RETURN){ + if((ct->flags & TOK_M_PARSELOAD_IARG1) == 0){ + ct->ctx.iarg1 = ct->ctx.caller_token->ctx.iarg1; + }/* End IF */ + if((ct->flags & TOK_M_PARSELOAD_IARG2) == 0){ + ct->ctx.iarg2 = ct->ctx.caller_token->ctx.iarg2; + }/* End IF */ + }/* End IF */ +/* + * If the next state is STOREVAL, the syntax stage is setting storeval. The + * typical example of this is when the syntax stage sees a constant like '1'. + * It uses this state to set the temporary value to 1. + */ + if( + ct->execute_state != EXEC_C_STOREVAL && + ((ct->flags & TOK_M_PARSELOAD_TMPVAL) == 0) + ){ + ct->ctx.tmpval = last_token->ctx.tmpval; + }/* End IF */ + + }/* End IF */ + + if(ct->execute_state){ +/* + * This is sort of a kludge, but it's an easy place to see if this + * state transition causes the buffer to become modified. If so, we + * link on an undo token to that if this gets undone, the modified + * status gets put back. If we didn't do it here, we would have to + * either do it in every command, and that would get tedious. + */ + old_modified_state = curbuf->ismodified; + old_curbuf = curbuf; + + if(trace_mode_flag == YES){ + trace_ct = *ct; + }/* End IF */ + + status = execute_a_state(ct,final_token); + if(status == BLOCKED){ + resume_execute_ct = ct; + } +/* + * An INVALIDATE status occurs when the execute engine changes the + * command token list in such a way that we may no longer be pointing + * at something valid. So we back out, and re-enter.. + */ + if(status == INVALIDATE){ + return(SUCCESS); + } + + if(trace_mode_flag == YES){ + trace_mode(TRACE_C_EXEC,&trace_ct,ct); + }/* End IF */ +/* + * If the modified state went from unmodified to modified, link on an + * undo token incase this gets undone - then the modified status will + * get put back to its original state. + */ + if(old_modified_state == NO && old_curbuf->ismodified == YES){ + if(ct->undo_list){ + ut = ct->undo_list; + while(ut->next_token) ut = ut->next_token; + ut->next_token = allocate_undo_token(NULL); + ut = ut->next_token; + }/* End IF */ + + else ct->undo_list = ut = allocate_undo_token(NULL); + if(ut != NULL){ + ut->opcode = UNDO_C_MODIFIED; + ut->carg1 = (char *)old_curbuf; + }/* End IF */ + }/* End IF */ + + if(susp_flag) cmd_pause(); +/* + * Since we are in immediate execute mode, if we get an error during the + * execution phase, we know it was generated by the most recently input + * character. Therefore, we can handle it the same way we do during the syntax + * parsing, by pretending a rubout character was input. + */ + if(status == BLOCKED) break; + if(intr_flag) break; + + if(status != SUCCESS){ + token_used = YES; + while(ct->next_token) ct = ct->next_token; + ct = parse_rubout_character(ct,0); + screen_reset_echo(cmd_list); + break; + }/* End IF */ + }/* End IF */ + + last_token = ct; + + if(jump_to_token){ + ct = jump_to_token; + jump_to_token = NULL; + continue; + } + + if(ct->next_token) ct = ct->next_token; + else break; + }/* End While */ +/* + * Now regardless of whether go_flag is set, chain ct forward to the end of + * the token list. + */ + while(ct->next_token) ct = ct->next_token; +/* + * If we hit FINALSTATE, this is the way the state machine has of telling us + * to reset to the begining parser state. We remember the final token address + * so that all additional undo tokens can be chained off of it. + */ + if(ct->ctx.state == STATE_C_FINALSTATE){ + return(0); + }/* End IF */ + + }/* End While */ + +}/* End Routine */ + + + +/* TECMACRO - Here to execute a Q register as a macro + * + * Function: + * + * This routine executes the specified q register as a macro. It returns + * SUCCESS or FAILURE depending on what happens within the macro. Note + * that it is very similar to the main parse routine. There are two major + * differences: First, the characters are gotton from the q-register + * rather than the input stream, and second, the entire parse is done + * to completion before any execution takes place. This means that the + * macro can modify the contents of the q-register without changing the + * way the macro will execute. + */ +int +tecmacro(qbp,input_ct,macro_cmd_list) +register struct buff_header *qbp; +struct cmd_token *input_ct; +struct cmd_token **macro_cmd_list; +{ +register struct cmd_token *ct; +struct cmd_token trace_ct; +register struct cmd_token *last_token; +int c = 0; +int status; +char token_used; +int i; +char old_modified_state; +struct buff_header *old_curbuf; +struct undo_token *ut; + + PREAMBLE(); + +/* + * We start our command token list with a special token that simply means that + * this is the begining of the list. We also set the current state to be the + * initial state of the parser. + */ + ct = allocate_cmd_token((struct cmd_token *)NULL); + if(ct == NULL) return(FAIL); + + ct->opcode = TOK_C_FIRSTTOKEN; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->ctx.go_flag = NO; +/* + * By setting STATUS_PASSED, we insure that the first command in the macro will + * get any arguments we have been passed. + */ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + ct->ctx.iarg1_flag = input_ct->ctx.iarg1_flag; + ct->ctx.iarg2_flag = input_ct->ctx.iarg2_flag; + ct->ctx.iarg1 = input_ct->ctx.iarg1; + ct->ctx.iarg2 = input_ct->ctx.iarg2; + *macro_cmd_list = ct; +/* + * We set the token_used flag so that we will call the getc routine. This flag + * in general lets us remember whether or not a state transistion ate an input + * character or not. + */ + token_used = YES; +/* + * Now we loop until we reach a final state, or something horrible happens. + */ + i = 0; + + while(i <= qbp->zee){ +/* + * Allocate a command token to hold the next state transition in. The routine + * will automatically link it onto the end of the list. + */ + ct = allocate_cmd_token(ct); + if(ct == NULL) return(FAIL); +/* + * Here we check for the special RETURN state. Although all this could be done + * in the state machine itself, this was a good place to consolidate things. + * We pop the state machine stack a level so as to return to the calling state. + * Note that iarg1 and iarg2 are copied from the calling state, so that they + * are preserved. This means that the substate must return it's value through + * tmpval which is not preserved. + */ + if(ct->ctx.state == STATE_C_RETURN){ + ct->ctx.state = ct->ctx.return_state; + ct->ctx.return_state = ct->ctx.caller_token->ctx.return_state; + ct->ctx.iarg1_flag = ct->ctx.caller_token->ctx.iarg1_flag; + ct->ctx.iarg2_flag = ct->ctx.caller_token->ctx.iarg2_flag; + ct->ctx.iarg1 = ct->ctx.caller_token->ctx.iarg1; + ct->ctx.iarg2 = ct->ctx.caller_token->ctx.iarg2; + ct->ctx.caller_token = ct->ctx.caller_token->ctx.caller_token; + }/* End IF */ +/* + * Set up the comand token a little + */ + ct->opcode = TOK_C_COPYTOKEN; + ct->flags |= TOK_M_EAT_TOKEN; + ct->input_byte = c; +/* + * If the last state ate the input character, we need to fetch another from the + * command source. + */ + if(token_used == YES){ + if(i == qbp->zee) c = ESCAPE; + else c = buff_contents(qbp,i++); +#ifdef NEEDED_WHICH_I_DONT_THINK_IT_IS + if(parse_special_character(ct,c)){ + ct = *macro_cmd_list; + while(ct->next_token) ct = ct->next_token; + continue; + }/* End IF */ +#endif + ct->opcode = TOK_C_INPUTCHAR; + ct->input_byte = c; + }/* End IF */ + + token_used = NO; +/* + * Call the state machine to process this character. Although more command + * tokens may be added onto the list by the state machine, we leave ct where + * it is for the moment so that we can work forward through all the generated + * states. + */ + parse_input_character(ct,*macro_cmd_list); +/* + * If the state machine indicates that the input character was eaten, remember + * this fact. + */ + if(ct->flags & TOK_M_EAT_TOKEN) token_used = YES; +/* + * If this character would cause us to transition into an error state, we + * terminate the macro and indicate where the error was. + */ + if(ct->ctx.state == STATE_C_ERRORSTATE){ + token_used = YES; + while(ct->next_token) ct = ct->next_token; + while(ct->opcode != TOK_C_FIRSTTOKEN){ + ct = parse_rubout_character(ct,0); + }/* End While */ + return(FAIL); + }/* End IF*/ +/* + * Always chain ct forward over any generated tokens + */ + while(ct->next_token) ct = ct->next_token; +/* + * If we hit FINALSTATE, this is the way the state machine has of telling us + * to reset to the begining parser state. + */ + if(ct->ctx.state == STATE_C_FINALSTATE) break; + + }/* End While */ + +/* + * Now that the syntax parsing has been completed, we start execution of the + * macro. + */ + ct = last_token = *macro_cmd_list; + + while(ct){ + +/* + * If we hit the final state, we don't need to go any further. + */ + if(ct->ctx.state == STATE_C_FINALSTATE) break; + +/* + * Carry the argument values forward to this state. If the last state was a + * RETURN state, then only copy out the tmpval, and fetch the callers iargs. + */ + if((ct->flags & TOK_M_PARSELOAD_IARG1) == 0){ + ct->ctx.iarg1 = last_token->ctx.iarg1; + }/* End IF */ + if((ct->flags & TOK_M_PARSELOAD_IARG2) == 0){ + ct->ctx.iarg2 = last_token->ctx.iarg2; + }/* End IF */ + + if(ct->ctx.state == STATE_C_RETURN){ + if((ct->flags & TOK_M_PARSELOAD_IARG1) == 0){ + ct->ctx.iarg1 = ct->ctx.caller_token->ctx.iarg1; + }/* End IF */ + if((ct->flags & TOK_M_PARSELOAD_IARG2) == 0){ + ct->ctx.iarg2 = ct->ctx.caller_token->ctx.iarg2; + }/* End IF */ + }/* End IF */ +/* + * If the next state is STOREVAL, the syntax stage is setting storeval. The + * typical example of this is when the syntax stage sees a constant like '1'. + * It uses this state to set the temporary value to 1. + */ + if( + ct->execute_state != EXEC_C_STOREVAL && + ((ct->flags & TOK_M_PARSELOAD_TMPVAL) == 0) + ){ + ct->ctx.tmpval = last_token->ctx.tmpval; + }/* End IF */ + +/* + * If this token has an execute state, call the execute phase to implement it. + */ + if(trace_mode_flag == YES){ + trace_mode(TRACE_C_MACRO,ct,0); + }/* End IF */ + + if(ct->execute_state){ + + old_modified_state = curbuf->ismodified; + old_curbuf = curbuf; + + if(trace_mode_flag == YES){ + trace_ct = *ct; + }/* End IF */ + + status = execute_a_state(ct,*macro_cmd_list); + + if(trace_mode_flag == YES){ + trace_mode(TRACE_C_EXEC,&trace_ct,ct); + }/* End IF */ + + if(old_modified_state == NO && old_curbuf->ismodified == YES){ + if(ct->undo_list){ + ut = ct->undo_list; + while(ut->next_token) ut = ut->next_token; + ut->next_token = allocate_undo_token(NULL); + ut = ut->next_token; + }/* End IF */ + + else ct->undo_list = ut = allocate_undo_token(NULL); + if(ut != NULL){ + ut->opcode = UNDO_C_MODIFIED; + ut->carg1 = (char *)old_curbuf; + }/* End IF */ + }/* End IF */ + + if(status != SUCCESS) goto cleanup; + if(exit_flag == YES) goto cleanup; + if(intr_flag) goto cleanup; + if(susp_flag) cmd_pause(); + }/* End IF */ +/* + * Ok, now chain to the next token, or quit if done + */ + last_token = ct; + ct = ct->next_token; + if(jump_to_token){ + ct = jump_to_token; + last_token = ct; + jump_to_token = NULL; + }/* End IF */ + }/* End While */ + +cleanup: + + if(input_ct->ctx.flags & CTOK_M_STATUS_PASSED){ + input_ct->ctx.iarg1_flag = last_token->ctx.iarg1_flag; + input_ct->ctx.iarg1 = last_token->ctx.iarg1; + }/* End IF */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* PARSE_SPECIAL_CHARACTER - Test for and handle special characters + * + * Function: + * + * This routine is called on each input character to determine whether + * it requires special handling. It catches characters such as rubout, + * ^W, ^U. + */ +int +parse_special_character(ct,c) +register struct cmd_token *ct; +register int c; +{ +char state_seen; +int tmp; + + PREAMBLE(); + + switch(c){ + case RUBOUT: + ct = parse_rubout_character(ct,1); + break; + case CNTRL_U: +/* + * Get rid of any right at the deletion point + */ + if(ct->opcode == TOK_C_INPUTCHAR){ + ct = parse_rubout_character(ct,1); + }/* End IF */ + + while(ct != cmd_list){ + if(ct->opcode == TOK_C_INPUTCHAR && ct->input_byte == '\n'){ + break; + }/* End IF */ + ct = parse_rubout_character(ct,1); + }/* End While */ + break; + case CNTRL_W: + state_seen = 0; + tmp = 0; + + while(ct != cmd_list){ + if(ct->flags & TOK_M_WORDBOUNDARY){ + state_seen = 1; + break; + }/* End IF */ + + if(ct->opcode == TOK_C_INPUTCHAR){ + tmp += 1; + if(tmp == 1 && ct->input_byte == '\n'){ + preserve_rubout_char(ct->input_byte); + ct = parse_rubout_cmd_token(ct); + break; + }/* End IF */ + + if(ct->input_byte != ' ' && ct->input_byte != '\t') break; + preserve_rubout_char(ct->input_byte); + + }/* End IF */ + + ct = parse_rubout_cmd_token(ct); + + }/* End While */ + + while(ct != cmd_list){ + if(ct->flags & TOK_M_WORDBOUNDARY) state_seen = 1; + + if(ct->opcode == TOK_C_INPUTCHAR){ + if(state_seen){ + preserve_rubout_char(ct->input_byte); + ct = parse_rubout_cmd_token(ct); + break; + }/* End IF */ + + if(isspace((int)ct->input_byte)) break; + preserve_rubout_char(ct->input_byte); + + }/* End IF */ + + ct = parse_rubout_cmd_token(ct); + + }/* End While */ + + break; + + case CNTRL_R: + if(!unpreserve_rubout_char(ct)){ + error_message("^R no input tokens available"); + }/* End IF */ + return(1); + + default: + return(0); + }/* End Switch */ + + screen_reset_echo(cmd_list); + return(1); + +}/* End Routine */ + + + +/* PARSE_RUBOUT_CHARACTER - Rubout the most recent character + * + * Function: + * + * This routine is called when a rubout character is typed. We have to + * back the parser up to the state it was in before the original char + * was typed, as well as performing any undo functions to make sure + * that the edit buffer also gets backed up. + */ +struct cmd_token * +parse_rubout_character(ct,preserve_flag) +register struct cmd_token *ct; +int preserve_flag; +{ +register struct cmd_token *oct; +struct undo_token *ut; +char saved_opcode; + + PREAMBLE(); + + while(1){ + +/* + * If the preserve flag is on, we want to save any deleted bytes in the + * q-register that is used for this purpose. That way, the user can + * correct for mistakenly typing the wrong rubout code. + */ + if(preserve_flag && ct->opcode == TOK_C_INPUTCHAR){ + preserve_rubout_char(ct->input_byte); + }/* End IF */ + +/* + * For each of the command tokens, we need to undo all the chained undo + * tokens. + */ + while( (ut = ct->undo_list) != NULL ){ +/* + * Take the top undo token off of the list and change the listhead to point + * to it's child. + */ + ct->undo_list = ut->next_token; + ut->next_token = NULL; +/* + * Now call the undo routine to back out the changes to the edit buffer that + * this token calls for. Then place the token back on the free list. + */ + parser_undo(ut); + free_undo_token(ut); + + }/* End While */ +/* + * If this is TOK_C_FIRSTTOKEN, it means he is trying to rubout the head of the + * list. This is probably not such a hot idea... + */ + if(ct->opcode == TOK_C_FIRSTTOKEN) break; +/* + * We save the opcode so that after cleaning up the token, we will know whether + * it was an input character or not. + */ + saved_opcode = ct->opcode; + + oct = ct->prev_token; + if(oct == NULL) break; + oct->next_token = NULL; + ct->prev_token = NULL; + free_cmd_token(ct); + ct = oct; +/* + * If this was an input character, then we have backed up enough. + */ + if(saved_opcode == TOK_C_INPUTCHAR) break; + + }/* End While */ + + return(ct); + +}/* End Routine */ + + + +/* PARSE_RUBOUT_CMD_TOKEN - Rubout the most recent command token + * + * Function: + * + * This routine is called to remove the last token on the command + * list. It gets used in rubout / ^U / ^W processing. + */ +struct cmd_token * +parse_rubout_cmd_token(ct) +register struct cmd_token *ct; +{ +register struct cmd_token *oct; +struct undo_token *ut; + + PREAMBLE(); + +/* + * We need to undo all the undo tokens chained off of this command token. + */ + while( (ut = ct->undo_list) != NULL ){ +/* + * Take the top undo token off of the list and change the listhead to point + * to it's child. + */ + ct->undo_list = ut->next_token; + ut->next_token = NULL; +/* + * Now call the undo routine to back out the changes to the edit buffer that + * this token calls for. Then place the token back on the free list. + */ + parser_undo(ut); + free_undo_token(ut); + + }/* End While */ + + oct = ct->prev_token; + if(oct) oct->next_token = NULL; + ct->prev_token = NULL; + free_cmd_token(ct); + return(oct); + +}/* End Routine */ + + + +/* PARSER_GETC - Return next input character + * + * Function: + * + * This routine is called by the parser when another input byte is needed. + */ +int +parser_getc() +{ +register int i; +char inbuf[4]; + +#ifdef VMS + short qio_iosb[4]; +#endif /* VMS */ + + PREAMBLE(); + +/* + * If there are no unclaimed bytes in the buffer, we need to read one + * from the command channel. + */ + input_pending_flag = tty_input_pending(); + + if(input_pending_flag == NO){ + if(ring_audible_bell){ + term_putc(BELL); + ring_audible_bell = 0; + }/* End IF */ + screen_echo('\0'); + screen_format_windows(); + screen_refresh(); + }/* End IF */ + +#ifdef CHECKPOINT + if(checkpoint_enabled == YES && checkpoint_flag == YES){ + cmd_checkpoint(); + checkpoint_flag = NO; + }/* End IF */ +#endif /* CHECKPOINT */ + + while(1){ + +#ifdef UNIX + + if(susp_flag){ + pause_while_in_input_wait(); + continue; + }/* End IF */ + + if(resize_flag){ + screen_resize(); + }/* End IF */ + + waiting_for_input_flag = YES; + i = read(tty_input_chan,inbuf,1); + waiting_for_input_flag = NO; + + intr_flag = 0; + + if(i >= 0) break; + + if(errno == EINTR) continue; +#endif +#ifdef VMS + waiting_for_input_flag = YES; + i = sys$qiow(0,tty_input_chan,IO$_READVBLK|IO$M_NOECHO|IO$M_NOFILTR, + qio_iosb,0,0,inbuf,1,0,0,0,0); + waiting_for_input_flag = NO; + if(!(i & STS$M_SUCCESS)) exit(i); +if(qio_iosb[0] != 1) +printf("!!! iosb[0] is %d\n",qio_iosb[0]); + if(inbuf[0] == '\r') inbuf[0] = '\n'; + else if(inbuf[0] == '\n') inbuf[0] = '\r'; + if(inbuf[0] == '`') inbuf[0] = ESCAPE; + if(inbuf[0] == CNTRL_Z && suspend_is_okay_flag == YES){ + pause_while_in_input_wait(); + continue; + }/* End IF */ + + break; +#endif + perror("error reading command input"); + punt(errno); + }/* End While */ + + screen_reset_message(); + + input_pending_flag = tty_input_pending(); + + if(inbuf[0] == alternate_escape_character){ + return(ESCAPE); + }/* End IF */ + + if(inbuf[0] == main_delete_character){ + return(RUBOUT); + }/* End IF */ + + if(inbuf[0] == alternate_delete_character){ + return(RUBOUT); + }/* End IF */ + + return(inbuf[0]); + +}/* End Routine */ + + + +/* PRESERVE_RUBOUT_CHARCTER - Save rubbed out characters + * + * Function: + * + * This routine is called in response to the user rubbing out input + * characters with rubout, ^U, or ^W. Sometimes he does this by + * mistake, and can delete large amounts of typing unintentially. + * This routine saves these characters in a special Q-register so + * that he can get them back if he wants. + */ +void +preserve_rubout_char(the_byte) +char the_byte; +{ +register struct buff_header *qbp; + + PREAMBLE(); + +/* + * Get a pointer to the special Q-register which holds the characters which + * have been rubbed out. + */ + qbp = buff_qfind('@',1); + if(qbp == NULL){ + return; + }/* End IF */ + +/* + * Put the deleted byte into the Q-register where it can be retrieved by + * the user if so desired. + */ + buff_insert_char(qbp,0,the_byte); + +}/* End Routine */ + + + +/* UNPRESERVE_RUBOUT_CHAR - Poke a rubbed out char back into the parse + * + * Function: + * + * This routine is called on behalf of a ^R command which causes the + * most recent rubbed out character to be restored to the parse tree. + * The return code determines whether this was done okay or not. + */ +int +unpreserve_rubout_char(ct) +struct cmd_token *ct; +{ +register struct buff_header *qbp; +int c; + + PREAMBLE(); + +/* + * Get a pointer to the special Q-register which holds the characters which + * have been rubbed out. + */ + qbp = buff_qfind('@',1); + if(qbp == NULL){ + return(FAIL); + }/* End IF */ + +/* + * Make sure thre are some bytes in it + */ + if(qbp->zee <= 0) return(FAIL); + +/* + * Get the first byte saved in the Q-register + */ + c = buff_contents(qbp,0); + buff_delete(qbp,0,1); + + tecparse_syntax(c); + + return(SUCCESS); + +}/* End Routine */ + + + +/* PARSER_CLEAN_PRESERVE_LIST - Zero the rubout preserve Q-register + * + * Function: + * + * This routine is called at double-escape time to clean up the + * special Q-register so that it doesn't grow without bounds. + */ +void +parser_clean_preserve_list() +{ +register struct buff_header *qbp; + + PREAMBLE(); + +/* + * Get a pointer to the special Q-register which holds the characters which + * have been rubbed out. + */ + qbp = buff_qfind('@',1); + if(qbp == NULL){ + return; + }/* End IF */ + +/* + * Delete all the bytes in the Q-register + */ + buff_delete(qbp,0,qbp->zee); + +}/* End Routine */ + + + +/* PARSER_DUMP_COMMAND_LINE - Copy the current parse tree into a Q-register + * + * Function: + * + * This routine copies the input bytes from the current command string + * into the specified Q-register. + */ +void +parser_dump_command_line(qbp) +register struct buff_header *qbp; +{ +register struct cmd_token *ct; + + PREAMBLE(); + + ct = cmd_list; + + while(ct){ + if(ct->opcode == TOK_C_INPUTCHAR){ + buff_insert_char(qbp,qbp->zee,ct->input_byte); + }/* End IF */ + ct = ct->next_token; + }/* End While */ + +}/* End Routine */ + + + +/* PARSER_REPLACE_COMMAND_LINE - Replace current parse tree + * + * Function: + * + * This routine replaces the current command string with the contents + * the specified Q-register. + */ +int +parser_replace_command_line(qbp) +register struct buff_header *qbp; +{ +register struct cmd_token *ct; +register struct cmd_token *first_different_ct; +register int c; +char temp; +register char *command_buffer; +register int cb_zee; + + PREAMBLE(); + + cb_zee = 0; + command_buffer = tec_alloc(TYPE_C_CBUFF,qbp->zee); + if(command_buffer == NULL) return(FAIL); +/* + * Walk the parse list to find the first place there is a difference + * between the current parse list, and the one to be installed. When + * we find the first difference, remember it, and then start copying + * the changed bytes into our temporary Q-register. + */ + first_different_ct = NULL; + ct = cmd_list; + for(c = 0; c < qbp->zee - 1; c++){ + temp = buff_contents(qbp,c); + if(first_different_ct == NULL){ + while(ct && ct->opcode != TOK_C_INPUTCHAR){ + ct = ct->next_token; + }/* End While */ + if(ct && ct->input_byte != temp) first_different_ct = ct; + if(ct && ct->next_token) ct = ct->next_token; + }/* End IF */ + if(first_different_ct){ + command_buffer[cb_zee++] = temp; + }/* End IF */ + }/* End FOR */ + + if(ct != NULL && first_different_ct == NULL){ + while(ct->next_token){ + if(ct->opcode == TOK_C_INPUTCHAR) break; + ct = ct->next_token; + } + first_different_ct = ct; + } +/* + * We only have to do the following if there were any changed bytes at + * all. If he changed his mind and didn't modify the command list at all, + * nothing really has to happen at all. + */ + if(first_different_ct){ + while(first_different_ct->next_token != NULL && + first_different_ct->next_token->opcode != TOK_C_INPUTCHAR){ + first_different_ct = first_different_ct->next_token; + } + ct = cmd_list; + while(ct->next_token) ct = ct->next_token; +/* + * Remove from the tail of the parse list all the bytes up to and including + * the first different character. This leaves us with a parse list which is + * just the part that has not changed. + */ + temp = 1; + while(ct != cmd_list && temp != 0){ + if(ct == first_different_ct) temp = 0; + ct = parse_rubout_character(ct,0); + }/* End While */ + +/* + * Now we walk through the Q-register, adding these bytes to the end of the + * parse list. These are the bytes which are different from the original + * parse list. + */ + qbp->pos_cache.lbp = NULL; + input_pending_flag = YES; + for(c = 0; c < cb_zee; c++){ + temp = command_buffer[c]; + tecparse_syntax(temp); + }/* End While */ + }/* End IF */ + tec_release(TYPE_C_CBUFF,command_buffer); + parser_reset_echo(); + + return(SUCCESS); + +}/* End Routine */ + + + +/* ALLOCATE_CMD_TOKEN - Allocate a command token structure + * + * Function: + * + * This routine is called to allocate a command token structure. If there + * is one on the free list, it is used, otherwise we allocate one. If + * an old_token was specified, the context area is copied into the new + * token, which has the effect of rippling the context forward through + * the parse. + */ +struct cmd_token * +allocate_cmd_token(old_token) +register struct cmd_token *old_token; +{ +register struct cmd_token *ct; + + PREAMBLE(); + +/* + * Call the memory allocator for a command token block + */ + ct = (struct cmd_token *)tec_alloc(TYPE_C_CMD,sizeof(struct cmd_token)); + if(ct == NULL) return(NULL); + +/* + * We set up the initial state to be that of an empty token, with fields + * which are not set up yet set to their proper defaults. + */ + ct->opcode = TOK_C_UNUSED; + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = NO; + ct->ctx.iarg1 = ct->ctx.iarg2 = 0; + ct->ctx.carg = NULL; + ct->next_token = NULL; + ct->undo_list = NULL; + ct->execute_state = 0; + ct->flags = 0; + + ct->prev_token = NULL; + ct->ctx.state = 0; + ct->ctx.go_flag = NO; + ct->ctx.return_state = 0; + ct->ctx.caller_token = NULL; + ct->ctx.pnest = ct->ctx.inest = ct->ctx.cnest = 0; +/* + * If there was an old token specified, two things must happen. First, we + * have to link the new token onto the old token list. Second, we have to + * copy the context area from the old token into the new one. + */ + if(old_token){ + old_token->next_token = ct; + ct->prev_token = old_token; + ct->ctx = old_token->ctx; + }/* End IF */ + + return(ct); + +}/* End Routine */ + + + +/* FREE_CMD_TOKEN - Routine to place a cmd token on the free list + * + * Function: + * + * This routine is called with the address of the command token + * to be placed on the free list. + */ +void +free_cmd_token(ct) +register struct cmd_token *ct; +{ + + PREAMBLE(); + + tec_release(TYPE_C_CMD,(char *)ct); + +}/* End Routine */ + +/* PAUSE_WHILE_IN_INPUT_WAIT - Pauses with the terminal in a good state + * + * Function: + * + * This routine is called to pause the editor while we have been in + * an input wait state. The big deal here is that we want to remove + * the reverse video box that may be on the echo line before we pause + * back to the system command processor. + */ +void +pause_while_in_input_wait() +{ + + PREAMBLE(); + + screen_reset_echo(cmd_list); + screen_refresh(); + cmd_pause(); + screen_echo('\0'); + screen_refresh(); + +}/* End Routine */ + + + +/* PARSER_CLEANUP_CTLIST - Routine to deallocate all the blocks on a ct list + * + * Function: + * + * This function deallocates all the blocks on a command token list, + * including cmd_tokens and undo_tokens. + */ +void +parser_cleanup_ctlist(ct) +register struct cmd_token *ct; +{ +register struct cmd_token *oct; + + PREAMBLE(); + +/* + * For each command token, if it has a list of undo tokens connected we + * call the routine which knows how to clean them up. + */ + while(ct){ + if(ct->undo_list){ + tecundo_cleanup(ct->undo_list); + }/* End IF */ + + ct->undo_list = NULL; +/* + * Now we remember what the next token in the list is, and then call the + * routine to free the current one. + */ + oct = ct; + ct = ct->next_token; + free_cmd_token(oct); + + }/* End While */ + +}/* End Routine */ + + + +/* PARSE_ANY_ARGUMENTS - Called by routines that don't want any arguments + * + * Function: + * + * This function is called by states which don't want to accept any + * arguments. If there are any present, it will generate an error message + * and set the error state. + */ +int +parse_any_arguments(ct,cmd_name) +register struct cmd_token *ct; +char *cmd_name; +{ +char tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + +/* + * If either of the iarg flags is set, that means we received an argument. + */ + if(ct->ctx.iarg1_flag == YES || ct->ctx.iarg2_flag == YES){ + (void) strcpy(tmp_message,"?The "); + (void) strcat(tmp_message,cmd_name); + (void) strcat(tmp_message," command accepts no arguments"); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return(1); + }/* End IF */ + + return(0); + +}/* End Routine */ + +/* PARSE_MORE_THAN_ONE_ARG - Called by routines that only want one arg + * + * Function: + * + * This function is called by states which only want to accept one + * argument. If there are two present, it will generate an error message + * and set the error state. + */ +int +parse_more_than_one_arg(ct,cmd_name) +register struct cmd_token *ct; +char *cmd_name; +{ +char tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + +/* + * If the iarg2 flag is set, that means that we received two arguments when + * we really only want one. + */ + if(ct->ctx.iarg2_flag == YES){ + (void) strcpy(tmp_message,"?Two Arguments to "); + (void) strcat(tmp_message,cmd_name); + (void) strcat(tmp_message," not allowed"); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return(1); + }/* End IF */ + + return(0); + +}/* End Routine */ + + + +/* PARSE_ILLEGAL_BUFFER_POSITION - Check for illegal buffer positions + * + * Function: + * + * This routine is called to verify that buffer positions specified are + * legal. If they are not, it generates an error message. + */ +int +parse_illegal_buffer_position(pos1,pos2,cmd_name) +register int pos1; +register int pos2; +char *cmd_name; +{ +char illegal_position; +char tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + + illegal_position = 0; + if(pos1 < 0) illegal_position = 1; + if(pos2 < 0) illegal_position = 1; + if(pos1 > curbuf->zee) illegal_position = 1; + if(pos2 > curbuf->zee) illegal_position = 1; + + if(illegal_position == 0) return(0); + + (void) strcpy(tmp_message,"?Attempt to Move Pointer Off Page with "); + (void) strcat(tmp_message,cmd_name); + error_message(tmp_message); + return(1); + +}/* End Routine */ + + + +/* PARSER_RESET_ECHO - Re-echo the input line + * + * Function: + * + * This is just an entry point to restore the echo line from a module + * that doesn't want to know about the cmd_list structure. + */ +void +parser_reset_echo() +{ + PREAMBLE(); + + screen_reset_echo(cmd_list); + +}/* End Routine */ + + + +/* TRACE_MODE - Trace execution of commands + * + * Function: + * + * This routine is called at parse and execute time if tracing has been + * enabled by the ? command. It causes q-register ? to be filled with + * execution trace information. + */ +void +trace_mode(phase,ct0,ct1) +register int phase; +struct cmd_token *ct0; +struct cmd_token *ct1; +{ +register struct buff_header *qbp; +char tmp_message[LINE_BUFFER_SIZE]; +register char *cp; +register char *state_name; +char *trace_convert_state_to_name(); +char *trace_convert_opcode_to_name(); +char *trace_convert_exec_state_to_name(); + + PREAMBLE(); + + qbp = buff_qfind('?',1); + if(qbp == NULL) return; + + switch(phase){ + case TRACE_C_PARSE: + state_name = trace_convert_opcode_to_name(ct0->opcode); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message,"PARSE: 0x%08X Opcode %s %c", + (unsigned int)ct0, + state_name, +/* ct0->opcode == TOK_C_INPUTCHAR ? ct0->input_byte : ' '); */ + ct0->flags & TOK_M_EAT_TOKEN ? ct0->input_byte : ' '); + if(ct0->flags & TOK_M_EAT_TOKEN){ + strcat(tmp_message," TOK_M_EAT_TOKEN"); + }/* End IF */ + if(ct0->flags & TOK_M_WORDBOUNDARY){ + strcat(tmp_message," TOK_M_WORDBOUNDARY"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_IARG1){ + strcat(tmp_message," TOK_M_PARSELOAD_IARG1"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_IARG2){ + strcat(tmp_message," TOK_M_PARSELOAD_IARG2"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_TMPVAL){ + strcat(tmp_message," TOK_M_PARSELOAD_TMPVAL"); + }/* End IF */ + strcat(tmp_message,"\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + +/* int execute_state; */ + state_name = trace_convert_state_to_name(ct0->ctx.state); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message," Parser State: %s '%c'", + state_name,isprint((int)ct0->q_register) ? + ct0->q_register : ' '); + if(ct0->ctx.flags & CTOK_M_COLON_SEEN){ + strcat(tmp_message," CTOK_M_COLON_SEEN"); + }/* End IF */ + if(ct0->ctx.flags & CTOK_M_ATSIGN_SEEN){ + strcat(tmp_message," CTOK_M_ATSIGN_SEEN"); + }/* End IF */ + if(ct0->ctx.flags & CTOK_M_STATUS_PASSED){ + strcat(tmp_message," CTOK_M_STATUS_PASSED"); + }/* End IF */ + strcat(tmp_message,"\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + if(ct0->ctx.go_flag == 0){ + sprintf(tmp_message," Go flag clear\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + + if(ct0->ctx.pnest || ct0->ctx.inest || ct0->ctx.cnest){ + sprintf(tmp_message," PNEST: %d INEST %d CNEST %d\n", + ct0->ctx.pnest,ct0->ctx.inest,ct0->ctx.cnest); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + + sprintf(tmp_message," iarg1_flag %d iarg1 %d (0x%x)\n", + ct0->ctx.iarg1_flag,ct0->ctx.iarg1,ct0->ctx.iarg1); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," iarg2_flag %d iarg2 %d (0x%x)\n", + ct0->ctx.iarg2_flag,ct0->ctx.iarg2,ct0->ctx.iarg2); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," carg 0x%x\n",(unsigned int)ct0->ctx.carg); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," tmpval %d (0x%x)\n", + ct0->ctx.tmpval,ct0->ctx.tmpval); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + if(ct0->ctx.return_state || ct0->ctx.caller_token){ + state_name = ""; + if(ct0->ctx.return_state){ + state_name = + trace_convert_state_to_name(ct0->ctx.return_state); + if(state_name == NULL) state_name = "UNKNOWN"; + }/* End IF */ + sprintf(tmp_message," return_state %s caller_token 0x%x\n", + state_name,(unsigned int)ct0->ctx.caller_token); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + break; + + case TRACE_C_EXEC: + state_name = trace_convert_exec_state_to_name(ct0->execute_state); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message,"EXEC: 0x%08X Exec State: %s\n", + (unsigned int)ct1,state_name); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," iarg1_flag %d iarg1 %d (0x%x)", + ct0->ctx.iarg1_flag,ct0->ctx.iarg1,ct0->ctx.iarg1); + while(strlen(tmp_message) < 39) strcat(tmp_message," "); + strcat(tmp_message," "); + cp = &tmp_message[strlen(tmp_message)]; + sprintf(cp," iarg1_flag %d iarg1 %d (0x%x)\n", + ct1->ctx.iarg1_flag,ct1->ctx.iarg1,ct1->ctx.iarg1); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," iarg2_flag %d iarg2 %d (0x%x)", + ct0->ctx.iarg2_flag,ct0->ctx.iarg2,ct0->ctx.iarg2); + while(strlen(tmp_message) < 39) strcat(tmp_message," "); + strcat(tmp_message," "); + cp = &tmp_message[strlen(tmp_message)]; + sprintf(cp," iarg2_flag %d iarg2 %d (0x%x)\n", + ct1->ctx.iarg2_flag,ct1->ctx.iarg2,ct1->ctx.iarg2); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," carg 0x%x",(unsigned int)ct0->ctx.carg); + while(strlen(tmp_message) < 39) strcat(tmp_message," "); + strcat(tmp_message," "); + cp = &tmp_message[strlen(tmp_message)]; + sprintf(cp," carg 0x%x\n",(unsigned int)ct1->ctx.carg); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," tmpval %d (0x%x)", + ct0->ctx.tmpval,ct0->ctx.tmpval); + while(strlen(tmp_message) < 39) strcat(tmp_message," "); + strcat(tmp_message," "); + cp = &tmp_message[strlen(tmp_message)]; + sprintf(cp," tmpval %d (0x%x)\n", + ct1->ctx.tmpval,ct1->ctx.tmpval); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + break; + + case TRACE_C_MACRO: + state_name = trace_convert_opcode_to_name(ct0->opcode); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message,"MACRO PLACEHOLDER: 0x%08X Opcode %s %c", + (unsigned int)ct0, + state_name, +/* ct0->opcode == TOK_C_INPUTCHAR ? ct0->input_byte : ' '); */ + ct0->flags & TOK_M_EAT_TOKEN ? ct0->input_byte : ' '); + if(ct0->flags & TOK_M_EAT_TOKEN){ + strcat(tmp_message," TOK_M_EAT_TOKEN"); + }/* End IF */ + if(ct0->flags & TOK_M_WORDBOUNDARY){ + strcat(tmp_message," TOK_M_WORDBOUNDARY"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_IARG1){ + strcat(tmp_message," TOK_M_PARSELOAD_IARG1"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_IARG2){ + strcat(tmp_message," TOK_M_PARSELOAD_IARG2"); + }/* End IF */ + if(ct0->flags & TOK_M_PARSELOAD_TMPVAL){ + strcat(tmp_message," TOK_M_PARSELOAD_TMPVAL"); + }/* End IF */ + strcat(tmp_message,"\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + +/* int execute_state; */ + state_name = trace_convert_state_to_name(ct0->ctx.state); + if(state_name == NULL) state_name = "UNKNOWN"; + sprintf(tmp_message," Parser State: %s '%c'", + state_name, + isprint((int)ct0->q_register) ? ct0->q_register : ' '); + if(ct0->ctx.flags & CTOK_M_COLON_SEEN){ + strcat(tmp_message," CTOK_M_COLON_SEEN"); + }/* End IF */ + if(ct0->ctx.flags & CTOK_M_ATSIGN_SEEN){ + strcat(tmp_message," CTOK_M_ATSIGN_SEEN"); + }/* End IF */ + if(ct0->ctx.flags & CTOK_M_STATUS_PASSED){ + strcat(tmp_message," CTOK_M_STATUS_PASSED"); + }/* End IF */ + strcat(tmp_message,"\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + if(ct0->ctx.go_flag == 0){ + sprintf(tmp_message," Go flag clear\n"); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + + if(ct0->ctx.pnest || ct0->ctx.inest || ct0->ctx.cnest){ + sprintf(tmp_message," PNEST: %d INEST %d CNEST %d\n", + ct0->ctx.pnest,ct0->ctx.inest,ct0->ctx.cnest); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + + sprintf(tmp_message," iarg1_flag %d iarg1 %d (0x%x)\n", + ct0->ctx.iarg1_flag,ct0->ctx.iarg1,ct0->ctx.iarg1); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," iarg2_flag %d iarg2 %d (0x%x)\n", + ct0->ctx.iarg2_flag,ct0->ctx.iarg2,ct0->ctx.iarg2); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," carg 0x%x\n",(unsigned int)ct0->ctx.carg); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + sprintf(tmp_message," tmpval %d (0x%x)\n", + ct0->ctx.tmpval,ct0->ctx.tmpval); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + + if(ct0->ctx.return_state || ct0->ctx.caller_token){ + state_name = ""; + if(ct0->ctx.return_state){ + state_name = + trace_convert_state_to_name(ct0->ctx.return_state); + if(state_name == NULL) state_name = "UNKNOWN"; + }/* End IF */ + sprintf(tmp_message," return_state %s caller_token 0x%x\n", + state_name,(unsigned int)ct0->ctx.caller_token); + buff_insert(qbp,qbp->zee,tmp_message,strlen(tmp_message)); + }/* End IF */ + break; + + }/* End Switch */ + +} + +char * +trace_convert_opcode_to_name(opcode) +int opcode; +{ + + PREAMBLE(); + + switch(opcode){ + case TOK_C_UNUSED: + return("TOK_C_UNUSED"); + case TOK_C_FIRSTTOKEN: + return("TOK_C_FIRSTTOKEN"); + case TOK_C_INPUTCHAR: + return("TOK_C_INPUTCHAR"); + case TOK_C_COPYTOKEN: + return("TOK_C_COPYTOKEN"); + case TOK_C_ITERATION_BEGIN: + return("TOK_C_ITERATION_BEGIN"); + case TOK_C_ITERATION_END: + return("TOK_C_ITERATION_END"); + case TOK_C_CONDITIONAL_END: + return("TOK_C_CONDITIONAL_END"); + case TOK_C_LABEL_BEGIN: + return("TOK_C_LABEL_BEGIN"); + case TOK_C_LABEL_END: + return("TOK_C_LABEL_END"); + case TOK_C_GOTO_BEGIN: + return("TOK_C_GOTO_BEGIN"); + case TOK_C_GOTO_END: + return("TOK_C_GOTO_END"); + case TOK_C_FINALTOKEN: + return("TOK_C_FINALTOKEN"); + case TOK_C_INITIALSTATE: + return("TOK_C_INITIALSTATE"); + case TOK_C_CONDITIONAL_ELSE: + return("TOK_C_CONDITIONAL_ELSE"); + default: + return(NULL); + }/* End Switch */ + +}/* End Routine */ + +char * +trace_convert_state_to_name(state) +int state; +{ + + PREAMBLE(); + + switch(state){ + case STATE_C_INITIALSTATE: + return("STATE_C_INITIALSTATE"); + case STATE_C_MAINCOMMANDS: + return("STATE_C_MAINCOMMANDS"); + case STATE_C_ESCAPESEEN: + return("STATE_C_ESCAPESEEN"); + case STATE_C_ECOMMAND: + return("STATE_C_ECOMMAND"); + case STATE_C_ARG1: + return("STATE_C_ARG1"); + case STATE_C_ARG2: + return("STATE_C_ARG2"); + case STATE_C_EXPRESSION: + return("STATE_C_EXPRESSION"); + case STATE_C_OPERATOR: + return("STATE_C_OPERATOR"); + case STATE_C_PLUS: + return("STATE_C_PLUS"); + case STATE_C_MINUS: + return("STATE_C_MINUS"); + case STATE_C_TIMES: + return("STATE_C_TIMES"); + case STATE_C_DIVIDE: + return("STATE_C_DIVIDE"); + case STATE_C_MINUSSEEN: + return("STATE_C_MINUSSEEN"); + case STATE_C_SUBEXPRESSION: + return("STATE_C_SUBEXPRESSION"); + case STATE_C_OPERAND: + return("STATE_C_OPERAND"); + case STATE_C_NUMBER_SUBSTATE: + return("STATE_C_NUMBER_SUBSTATE"); + case STATE_C_QOPERAND: + return("STATE_C_QOPERAND"); + case STATE_C_INSERT: + return("STATE_C_INSERT"); + case STATE_C_QUOTED_INSERT: + return("STATE_C_QUOTED_INSERT"); + case STATE_C_UMINUS: + return("STATE_C_UMINUS"); + case STATE_C_LABEL: + return("STATE_C_LABEL"); + case STATE_C_UQREGISTER: + return("STATE_C_UQREGISTER"); + case STATE_C_WRITEFILE: + return("STATE_C_WRITEFILE"); + case STATE_C_STRING: + return("STATE_C_STRING"); + case STATE_C_SEARCH: + return("STATE_C_SEARCH"); + case STATE_C_FCOMMAND: + return("STATE_C_FCOMMAND"); + case STATE_C_FSPART1: + return("STATE_C_FSPART1"); + case STATE_C_FSPART2: + return("STATE_C_FSPART2"); + case STATE_C_EDITBUF: + return("STATE_C_EDITBUF"); + case STATE_C_READFILE: + return("STATE_C_READFILE"); + case STATE_C_XQREGISTER: + return("STATE_C_XQREGISTER"); + case STATE_C_GQREGISTER: + return("STATE_C_GQREGISTER"); + case STATE_C_MQREGISTER: + return("STATE_C_MQREGISTER"); + case STATE_C_VIEWBUF: + return("STATE_C_VIEWBUF"); + case STATE_C_FDCOMMAND: + return("STATE_C_FDCOMMAND"); + case STATE_C_CONDITIONALS: + return("STATE_C_CONDITIONALS"); + case STATE_C_GOTO: + return("STATE_C_GOTO"); + case STATE_C_FRPART1: + return("STATE_C_FRPART1"); + case STATE_C_FRPART2: + return("STATE_C_FRPART2"); + case STATE_C_MESSAGE: + return("STATE_C_MESSAGE"); + case STATE_C_FKCOMMAND: + return("STATE_C_FKCOMMAND"); + case STATE_C_ECCOMMAND: + return("STATE_C_ECCOMMAND"); + case STATE_C_SAVECOMMAND: + return("STATE_C_SAVECOMMAND"); + case STATE_C_PERCENT_OPERAND: + return("STATE_C_PERCENT_OPERAND"); + case STATE_C_ATINSERT: + return("STATE_C_ATINSERT"); + case STATE_C_ATINSERT_PART2: + return("STATE_C_ATINSERT_PART2"); + case STATE_C_ONE_EQUALS: + return("STATE_C_ONE_EQUALS"); + case STATE_C_TWO_EQUALS: + return("STATE_C_TWO_EQUALS"); + case STATE_C_SKIP_ELSE: + return("STATE_C_SKIP_ELSE"); + case STATE_C_PUSH_QREGISTER: + return("STATE_C_PUSH_QREGISTER"); + case STATE_C_POP_QREGISTER: + return("STATE_C_POP_QREGISTER"); + case STATE_C_NSEARCH: + return("STATE_C_NSEARCH"); + case STATE_C_ACCEPT_ARGS: + return("STATE_C_ACCEPT_ARGS"); + case STATE_C_EQQREGISTER1: + return("STATE_C_EQQREGISTER1"); + case STATE_C_EQQREGISTER2: + return("STATE_C_EQQREGISTER2"); + case STATE_C_RADIX: + return("STATE_C_RADIX"); + case STATE_C_HEX_NUMBER: + return("STATE_C_HEX_NUMBER"); + case STATE_C_OCTAL_NUMBER: + return("STATE_C_OCTAL_NUMBER"); + case STATE_C_HEX_NUMBER_SUBSTATE: + return("STATE_C_HEX_NUMBER_SUBSTATE"); + case STATE_C_OCTAL_NUMBER_SUBSTATE: + return("STATE_C_OCTAL_NUMBER_SUBSTATE"); + case STATE_C_BACKSLASH: + return("STATE_C_BACKSLASH"); + case STATE_C_DELAYED_MINUS: + return("STATE_C_DELAYED_MINUS"); + case STATE_C_DELAYED_PLUS: + return("STATE_C_DELAYED_PLUS"); + case STATE_C_FSPART3: + return("STATE_C_FSPART3"); + + + case STATE_C_RETURN: + return("STATE_C_RETURN"); + case STATE_C_FINALSTATE: + return("STATE_C_FINALSTATE"); + case STATE_C_ERRORSTATE: + return("STATE_C_ERRORSTATE"); + default: + return(NULL); + }/* End Switch */ + +}/* End Routine */ + + + +char * +trace_convert_exec_state_to_name(state) +int state; +{ + + PREAMBLE(); + + switch(state){ + case EXEC_C_NULLSTATE: + return("EXEC_C_NULLSTATE"); + case EXEC_C_DOTARG1: + return("EXEC_C_DOTARG1"); + case EXEC_C_ZEEARG1: + return("EXEC_C_ZEEARG1"); + case EXEC_C_HARGUMENT: + return("EXEC_C_HARGUMENT"); + case EXEC_C_EXITCOMMAND: + return("EXEC_C_EXITCOMMAND"); + case EXEC_C_UQREGISTER: + return("EXEC_C_UQREGISTER"); + case EXEC_C_JUMP: + return("EXEC_C_JUMP"); + case EXEC_C_INSERT: + return("EXEC_C_INSERT"); + case EXEC_C_LINE: + return("EXEC_C_LINE"); + case EXEC_C_CHAR: + return("EXEC_C_CHAR"); + case EXEC_C_RCHAR: + return("EXEC_C_RCHAR"); + case EXEC_C_DELETE: + return("EXEC_C_DELETE"); + case EXEC_C_HVALUE: + return("EXEC_C_HVALUE"); + case EXEC_C_DOTVALUE: + return("EXEC_C_DOTVALUE"); + case EXEC_C_ZEEVALUE: + return("EXEC_C_ZEEVALUE"); + case EXEC_C_QVALUE: + return("EXEC_C_QVALUE"); + case EXEC_C_EQUALS: + return("EXEC_C_EQUALS"); + case EXEC_C_REDRAW_SCREEN: + return("EXEC_C_REDRAW_SCREEN"); + case EXEC_C_STOREVAL: + return("EXEC_C_STOREVAL"); + case EXEC_C_STORE1: + return("EXEC_C_STORE1"); + case EXEC_C_STORE2: + return("EXEC_C_STORE2"); + case EXEC_C_UMINUS: + return("EXEC_C_UMINUS"); + case EXEC_C_PLUS: + return("EXEC_C_PLUS"); + case EXEC_C_MINUS: + return("EXEC_C_MINUS"); + case EXEC_C_TIMES: + return("EXEC_C_TIMES"); + case EXEC_C_DIVIDE: + return("EXEC_C_DIVIDE"); + case EXEC_C_KILL: + return("EXEC_C_KILL"); + case EXEC_C_WRITEFILE: + return("EXEC_C_WRITEFILE"); + case EXEC_C_SEARCH: + return("EXEC_C_SEARCH"); + case EXEC_C_SETSEARCH: + return("EXEC_C_SETSEARCH"); + case EXEC_C_FSREPLACE1: + return("EXEC_C_FSREPLACE1"); + case EXEC_C_ITERATION_BEGIN: + return("EXEC_C_ITERATION_BEGIN"); + case EXEC_C_ITERATION_END: + return("EXEC_C_ITERATION_END"); + case EXEC_C_READFILE: + return("EXEC_C_READFILE"); + case EXEC_C_EDITBUF: + return("EXEC_C_EDITBUF"); + case EXEC_C_XQREGISTER: + return("EXEC_C_XQREGISTER"); + case EXEC_C_GQREGISTER: + return("EXEC_C_GQREGISTER"); + case EXEC_C_SEMICOLON: + return("EXEC_C_SEMICOLON"); + case EXEC_C_MQREGISTER: + return("EXEC_C_MQREGISTER"); + case EXEC_C_CLOSEBUF: + return("EXEC_C_CLOSEBUF"); + case EXEC_C_VIEWBUF: + return("EXEC_C_VIEWBUF"); + case EXEC_C_FDCOMMAND: + return("EXEC_C_FDCOMMAND"); + case EXEC_C_ACOMMAND: + return("EXEC_C_ACOMMAND"); + case EXEC_C_BACKSLASH: + return("EXEC_C_BACKSLASH"); + case EXEC_C_BACKSLASHARG: + return("EXEC_C_BACKSLASHARG"); + case EXEC_C_COND_GT: + return("EXEC_C_COND_GT"); + case EXEC_C_COND_LT: + return("EXEC_C_COND_LT"); + case EXEC_C_COND_EQ: + return("EXEC_C_COND_EQ"); + case EXEC_C_COND_NE: + return("EXEC_C_COND_NE"); + case EXEC_C_COND_DIGIT: + return("EXEC_C_COND_DIGIT"); + case EXEC_C_COND_ALPHA: + return("EXEC_C_COND_ALPHA"); + case EXEC_C_COND_LOWER: + return("EXEC_C_COND_LOWER"); + case EXEC_C_COND_UPPER: + return("EXEC_C_COND_UPPER"); + case EXEC_C_COND_SYMBOL: + return("EXEC_C_COND_SYMBOL"); + case EXEC_C_GOTO: + return("EXEC_C_GOTO"); + case EXEC_C_FRREPLACE: + return("EXEC_C_FRREPLACE"); + case EXEC_C_MESSAGE: + return("EXEC_C_MESSAGE"); + case EXEC_C_RESET_MESSAGE: + return("EXEC_C_RESET_MESSAGE"); + case EXEC_C_OUTPUT_MESSAGE: + return("EXEC_C_OUTPUT_MESSAGE"); + case EXEC_C_FKCOMMAND: + return("EXEC_C_FKCOMMAND"); + case EXEC_C_REMEMBER_DOT: + return("EXEC_C_REMEMBER_DOT"); + case EXEC_C_ECCOMMAND: + return("EXEC_C_ECCOMMAND"); + case EXEC_C_SAVECOMMAND: + return("EXEC_C_SAVECOMMAND"); + case EXEC_C_SCROLL: + return("EXEC_C_SCROLL"); + case EXEC_C_UPDATE_SCREEN: + return("EXEC_C_UPDATE_SCREEN"); + case EXEC_C_SET_IMMEDIATE_MODE: + return("EXEC_C_SET_IMMEDIATE_MODE"); + case EXEC_C_PERCENT_VALUE: + return("EXEC_C_PERCENT_VALUE"); + case EXEC_C_WORD: + return("EXEC_C_WORD"); + case EXEC_C_TWO_EQUALS: + return("EXEC_C_TWO_EQUALS"); + case EXEC_C_THREE_EQUALS: + return("EXEC_C_THREE_EQUALS"); + case EXEC_C_SKIP_ELSE: + return("EXEC_C_SKIP_ELSE"); + case EXEC_C_PUSH_QREGISTER: + return("EXEC_C_PUSH_QREGISTER"); + case EXEC_C_POP_QREGISTER: + return("EXEC_C_POP_QREGISTER"); + case EXEC_C_NSEARCH: + return("EXEC_C_NSEARCH"); + case EXEC_C_EQQREGISTER: + return("EXEC_C_EQQREGISTER"); + case EXEC_C_WINDOW_CONTROL: + return("EXEC_C_WINDOW_CONTROL"); + case EXEC_C_NEXT_WINDOW: + return("EXEC_C_NEXT_WINDOW"); + case EXEC_C_RLINE: + return("EXEC_C_RLINE"); + case EXEC_C_DELWORD: + return("EXEC_C_DELWORD"); + case EXEC_C_RDELWORD: + return("EXEC_C_RDELWORD"); + case EXEC_C_OPENBRACE: + return("EXEC_C_OPENBRACE"); + case EXEC_C_CLOSEBRACE: + return("EXEC_C_CLOSEBRACE"); + case EXEC_C_SKIPLABEL: + return("EXEC_C_SKIPLABEL"); + case EXEC_C_SETOPTIONS: + return("EXEC_C_SETOPTIONS"); + case EXEC_C_FSREPLACE2: + return("EXEC_C_FSREPLACE2"); + case EXEC_C_FSREPLACE3: + return("EXEC_C_FSREPLACE3"); + default: + return(NULL); + }/* End Switch */ + +}/* End Routine */ diff --git a/tecparse.h b/tecparse.h new file mode 100644 index 0000000..3ae0268 --- /dev/null +++ b/tecparse.h @@ -0,0 +1,327 @@ +/* + * $Date: 2007/12/10 21:59:20 $ + * $Source: /cvsroot/videoteco/videoteco/tecparse.h,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecparse.h + * Definitions for TECO parser + * %W% (PC) %G% + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +/* + * This structure holds the context which must be passed forward as the + * parse progresses. We split it out of the cmd_token structure so that + * it can be easily duplicated. + */ +struct cmd_context { + char state; + char flags; + char go_flag; + char pnest; + char inest; + char cnest; + char iarg1_flag; + char iarg2_flag; + char delimeter; + int iarg1; + int iarg2; + char *carg; + int tmpval; + int return_state; + struct cmd_token *caller_token; +}; + +/* + * Define the structure which we use to describe commands + */ +struct cmd_token { + char opcode; + char input_byte; + char q_register; + char flags; + int execute_state; + struct cmd_token *next_token; + struct cmd_token *prev_token; + struct undo_token *undo_list; + struct cmd_context ctx; +}; + +struct undo_token { + char opcode; + int iarg1; + int iarg2; + char *carg1; + struct undo_token *next_token; +}; + +#define TRACE_C_PARSE 1 +#define TRACE_C_EXEC 2 +#define TRACE_C_MACRO 3 + +#define TOK_M_EAT_TOKEN (1 << 0) +#define TOK_M_WORDBOUNDARY (1 << 1) +#define TOK_M_PARSELOAD_IARG1 (1 << 2) +#define TOK_M_PARSELOAD_IARG2 (1 << 3) +#define TOK_M_PARSELOAD_TMPVAL (1 << 4) + +#define CTOK_M_COLON_SEEN (1 << 0) +#define CTOK_M_ATSIGN_SEEN (1 << 1) +#define CTOK_M_STATUS_PASSED (1 << 2) + +#define TOK_C_UNUSED 0 /* Value before it gets set */ +#define TOK_C_FIRSTTOKEN 1 /* The begining of it all */ +#define TOK_C_INPUTCHAR 2 /* An input character was recieved here */ +#define TOK_C_COPYTOKEN 3 /* A copy of a previous command token */ +#define TOK_C_ITERATION_BEGIN 4 /* Marks the begining of an iteration */ +#define TOK_C_ITERATION_END 5 /* Marks the end of an iteration */ +#define TOK_C_CONDITIONAL_END 6 /* Marks end of a conditional cmd */ +#define TOK_C_LABEL_BEGIN 7 /* Begining of a label tag !like this! */ +#define TOK_C_LABEL_END 8 /* End of a label tag */ +#define TOK_C_GOTO_BEGIN 9 /* Begining of an Omumble$ string */ +#define TOK_C_GOTO_END 10 /* End of an Omumble$ string */ +#define TOK_C_FINALTOKEN 11 /* Like FINALSTATE */ +#define TOK_C_INITIALSTATE 12 /* Used by ^W to find begining of cmds */ +#define TOK_C_CONDITIONAL_ELSE 13/* Marks begining of an ELSE clause */ + +#define STATE_C_INITIALSTATE 0 +#define STATE_C_MAINCOMMANDS 1 +#define STATE_C_ESCAPESEEN 2 +#define STATE_C_ECOMMAND 3 +#define STATE_C_ARG1 4 +#define STATE_C_ARG2 5 +#define STATE_C_EXPRESSION 6 +#define STATE_C_OPERATOR 7 +#define STATE_C_PLUS 8 +#define STATE_C_MINUS 9 +#define STATE_C_TIMES 10 +#define STATE_C_DIVIDE 11 +#define STATE_C_MINUSSEEN 14 +#define STATE_C_SUBEXPRESSION 15 +#define STATE_C_OPERAND 16 +#define STATE_C_NUMBER_SUBSTATE 17 +#define STATE_C_QOPERAND 18 +#define STATE_C_INSERT 19 +#define STATE_C_QUOTED_INSERT 20 +#define STATE_C_UMINUS 21 +#define STATE_C_LABEL 22 +#define STATE_C_UQREGISTER 23 +#define STATE_C_WRITEFILE 24 +#define STATE_C_STRING 25 +#define STATE_C_STRING1 26 +#define STATE_C_SEARCH 27 +#define STATE_C_FCOMMAND 28 +#define STATE_C_FSPART1 29 +#define STATE_C_FSPART2 30 +#define STATE_C_EDITBUF 31 +#define STATE_C_READFILE 32 +#define STATE_C_XQREGISTER 33 +#define STATE_C_GQREGISTER 34 +#define STATE_C_MQREGISTER 35 +#define STATE_C_VIEWBUF 36 +#define STATE_C_FDCOMMAND 37 +#define STATE_C_CONDITIONALS 38 +#define STATE_C_GOTO 39 +#define STATE_C_FRPART1 40 +#define STATE_C_FRPART2 41 +#define STATE_C_MESSAGE 42 +#define STATE_C_FKCOMMAND 43 +#define STATE_C_ECCOMMAND 44 +#define STATE_C_SAVECOMMAND 45 +#define STATE_C_PERCENT_OPERAND 46 +#define STATE_C_ATINSERT 47 +#define STATE_C_ATINSERT_PART2 48 +#define STATE_C_ONE_EQUALS 49 +#define STATE_C_TWO_EQUALS 50 +#define STATE_C_SKIP_ELSE 51 +#define STATE_C_PUSH_QREGISTER 52 +#define STATE_C_POP_QREGISTER 53 +#define STATE_C_NSEARCH 54 +#define STATE_C_ACCEPT_ARGS 55 +#define STATE_C_EQQREGISTER1 56 +#define STATE_C_EQQREGISTER2 57 +#define STATE_C_RADIX 58 +#define STATE_C_HEX_NUMBER 59 +#define STATE_C_OCTAL_NUMBER 60 +#define STATE_C_HEX_NUMBER_SUBSTATE 61 +#define STATE_C_OCTAL_NUMBER_SUBSTATE 62 +#define STATE_C_BACKSLASH 63 +#define STATE_C_DELAYED_MINUS 64 +#define STATE_C_DELAYED_PLUS 65 +#define STATE_C_FSPART3 66 +#define STATE_C_FTAGS 67 + + +#define STATE_C_RETURN 97 +#define STATE_C_FINALSTATE 98 +#define STATE_C_ERRORSTATE 99 + +/* + * Here we define the execution time states + */ +#define EXEC_C_NULLSTATE 0 +#define EXEC_C_DOTARG1 1 +#define EXEC_C_ZEEARG1 2 +#define EXEC_C_HARGUMENT 3 +#define EXEC_C_EXITCOMMAND 4 +#define EXEC_C_UQREGISTER 5 +#define EXEC_C_JUMP 8 +#define EXEC_C_INSERT 9 +#define EXEC_C_LINE 10 +#define EXEC_C_CHAR 11 +#define EXEC_C_RCHAR 12 +#define EXEC_C_DELETE 13 +#define EXEC_C_HVALUE 14 +#define EXEC_C_DOTVALUE 15 +#define EXEC_C_ZEEVALUE 16 +#define EXEC_C_QVALUE 17 +#define EXEC_C_EQUALS 18 +#define EXEC_C_REDRAW_SCREEN 19 +#define EXEC_C_STOREVAL 20 +#define EXEC_C_STORE1 21 +#define EXEC_C_STORE2 22 +#define EXEC_C_UMINUS 23 +#define EXEC_C_PLUS 24 +#define EXEC_C_MINUS 25 +#define EXEC_C_TIMES 26 +#define EXEC_C_DIVIDE 27 +#define EXEC_C_KILL 28 +#define EXEC_C_WRITEFILE 29 +#define EXEC_C_SEARCH 30 +#define EXEC_C_SETSEARCH 31 +#define EXEC_C_FSREPLACE1 32 +#define EXEC_C_ITERATION_BEGIN 33 +#define EXEC_C_ITERATION_END 34 +#define EXEC_C_READFILE 35 +#define EXEC_C_EDITBUF 36 +#define EXEC_C_XQREGISTER 37 +#define EXEC_C_GQREGISTER 38 +#define EXEC_C_SEMICOLON 39 +#define EXEC_C_MQREGISTER 40 +#define EXEC_C_CLOSEBUF 41 +#define EXEC_C_VIEWBUF 42 +#define EXEC_C_FDCOMMAND 43 +#define EXEC_C_ACOMMAND 44 +#define EXEC_C_BACKSLASH 45 +#define EXEC_C_BACKSLASHARG 46 +#define EXEC_C_COND_GT 47 +#define EXEC_C_COND_LT 48 +#define EXEC_C_COND_EQ 49 +#define EXEC_C_COND_NE 50 +#define EXEC_C_COND_DIGIT 51 +#define EXEC_C_COND_ALPHA 52 +#define EXEC_C_COND_LOWER 53 +#define EXEC_C_COND_UPPER 54 +#define EXEC_C_COND_SYMBOL 55 +#define EXEC_C_GOTO 56 +#define EXEC_C_FRREPLACE 57 +#define EXEC_C_MESSAGE 58 +#define EXEC_C_RESET_MESSAGE 59 +#define EXEC_C_OUTPUT_MESSAGE 60 +#define EXEC_C_FKCOMMAND 61 +#define EXEC_C_REMEMBER_DOT 62 +#define EXEC_C_ECCOMMAND 63 +#define EXEC_C_SAVECOMMAND 64 +#define EXEC_C_SCROLL 65 +#define EXEC_C_UPDATE_SCREEN 66 +#define EXEC_C_SET_IMMEDIATE_MODE 67 +#define EXEC_C_PERCENT_VALUE 68 +#define EXEC_C_WORD 69 +#define EXEC_C_TWO_EQUALS 70 +#define EXEC_C_THREE_EQUALS 71 +#define EXEC_C_SKIP_ELSE 72 +#define EXEC_C_PUSH_QREGISTER 73 +#define EXEC_C_POP_QREGISTER 74 +#define EXEC_C_NSEARCH 75 +#define EXEC_C_EQQREGISTER 76 +#define EXEC_C_WINDOW_CONTROL 77 +#define EXEC_C_NEXT_WINDOW 78 +#define EXEC_C_RLINE 79 +#define EXEC_C_DELWORD 80 +#define EXEC_C_RDELWORD 81 +#define EXEC_C_OPENBRACE 82 +#define EXEC_C_CLOSEBRACE 83 +#define EXEC_C_SKIPLABEL 84 +#define EXEC_C_SETOPTIONS 85 +#define EXEC_C_FSREPLACE2 86 +#define EXEC_C_FSREPLACE3 87 +#define EXEC_C_FTAGS 88 + +/* + * Define the UNDO symbols + */ +#define UNDO_C_UNUSED 0 +#define UNDO_C_CHANGEDOT 1 +#define UNDO_C_DELETE 2 +#define UNDO_C_INSERT 3 +#define UNDO_C_UQREGISTER 4 +#define UNDO_C_MEMFREE 5 +#define UNDO_C_SHORTEN_STRING 6 +#define UNDO_C_SET_SEARCH_STRING 7 +#define UNDO_C_CHANGEBUFF 8 +#define UNDO_C_MACRO 9 +#define UNDO_C_SHORTEN_MESSAGE 10 +#define UNDO_C_BULK_INSERT 11 +#define UNDO_C_SET_IMMEDIATE_MODE 12 +#define UNDO_C_PUSH 13 +#define UNDO_C_POP 14 +#define UNDO_C_MODIFIED 15 +#define UNDO_C_CLOSEBUFF 16 +#define UNDO_C_WINDOW_SWITCH 17 +#define UNDO_C_WINDOW_SPLIT 18 +#define UNDO_C_REOPENBUFF 19 +#define UNDO_C_RENAME_BUFFER 20 +#define UNDO_C_PRESERVEARGS 21 +#define UNDO_C_SETOPTIONS 22 +#define UNDO_C_SET_SEARCH_GLOBALS 23 +#define UNDO_C_LOAD_TAGS 24 +#define UNDO_C_SELECT_TAGS 25 +#define UNDO_C_SET_EXIT_FLAG 26 + +int cmd_oscmd(struct cmd_token *ct); +int buff_insert_from_buffer_with_undo( struct cmd_token *, + struct buff_header *,int,struct buff_header *,int,int); +int buff_delete_with_undo( struct cmd_token *,struct buff_header *,int,int); +int buff_insert_with_undo( struct cmd_token *, + struct buff_header *,int,char *,int); +int rename_edit_buffer(struct buff_header *,char *,struct cmd_token *); +int cmd_setoptions(int,int,struct undo_token *); +void tag_dump_database(struct tags *tp,struct cmd_token *uct); +int buff_insert_char_with_undo(struct cmd_token *, + struct buff_header *,int,char); +int set_search_string_with_undo(char *,struct cmd_token *uct); +int cmd_tags(struct cmd_token *uct,int,int,int,char *); +int tecmacro(struct buff_header *,struct cmd_token *,struct cmd_token **); +void parser_cleanup_ctlist(struct cmd_token *); +void free_cmd_token(struct cmd_token *); +int parser_undo(struct undo_token *); +void free_undo_token(struct undo_token *); +void screen_reset_echo(struct cmd_token *); +int parse_special_character(struct cmd_token *,int); +void parse_input_character(struct cmd_token *,struct cmd_token *); +void trace_mode(int, struct cmd_token *, struct cmd_token *); +int execute_a_state(struct cmd_token *, struct cmd_token *); +void tecundo_cleanup(struct undo_token *); +int parse_any_arguments(struct cmd_token *,char *); +int parse_more_than_one_arg(struct cmd_token *,char *); +int parse_check_qname(struct cmd_token *,char); +struct cmd_token *allocate_cmd_token(struct cmd_token *old_token); +struct undo_token *allocate_undo_token(struct cmd_token *ct); +void buff_free_line_buffer(struct buff_line *); + diff --git a/tecstate.c b/tecstate.c new file mode 100644 index 0000000..d908e74 --- /dev/null +++ b/tecstate.c @@ -0,0 +1,2374 @@ +char *tecstate_c_version = "tecstate.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:21 $ + * $Source: /cvsroot/videoteco/videoteco/tecstate.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecstate.c + * Main SWITCH/CASE statements to implement the parser syntax stage + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + extern char immediate_execute_flag; + extern char trace_mode_flag; + extern char suspend_is_okay_flag; + + + +/* PARSE_INPUT_CHARACTER - Continue the parse with the supplied character + * + * Function: + * + * We re-enter the parser with a new character at this point. We jump back + * to the state we left before. + */ +void +parse_input_character(ct,uct) +register struct cmd_token *ct; +struct cmd_token *uct; +{ +char tmp_message[LINE_BUFFER_SIZE]; +register struct cmd_token *oct = NULL; + + PREAMBLE(); + + switch(ct->ctx.state){ +/* + * Here on initial command state. This is the begining of a command, so we are + * looking for arguments that might go with a command. If there are no args, + * we just transfer into the main command loop. + */ + case STATE_C_INITIALSTATE: + ct->ctx.flags &= ~(CTOK_M_COLON_SEEN | CTOK_M_ATSIGN_SEEN); + ct->flags |= TOK_M_WORDBOUNDARY; + case STATE_C_ACCEPT_ARGS: + ct->ctx.carg = NULL; + switch(ct->input_byte){ +/* + * If it looks like an argument, then transfer into a subexpression parser to + * get the value of the expression. ARG1 will stuff the result into iarg1 and + * also check for a comma (',') incase he is specifing a twin argument command + */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'Q': case 'q': case '%': +#ifdef CLASSIC_B_BEHAVIOR + case 'B': case 'b': +#endif + case 'Z': case 'z': + case '\\': + case '.': + case '-': + case '(': + case '^': + ct->ctx.flags &= ~CTOK_M_STATUS_PASSED; + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = NO; + ct->ctx.state = STATE_C_EXPRESSION; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_ARG1; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; +/* + * H is a special case, since the single argument actually implies two args. + * (H actually is the same as 0,z) + */ + case 'H': case 'h': + ct->ctx.flags &= ~CTOK_M_STATUS_PASSED; + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = YES; + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->execute_state = EXEC_C_HVALUE; + return; +/* + * Here on the @ command. This says to use user specified delimeters for + * strings, rather than just terminating with escape. + */ + case '@': + ct->ctx.flags |= CTOK_M_ATSIGN_SEEN; + ct->ctx.state = STATE_C_ACCEPT_ARGS; + return; +/* + * Here on the : command. This is just a flag to many commands to tell them + * to work in a slightly different way. The most common usage is to mean that + * the command should return a value which says whether it worked or not. + */ + case ':': + ct->ctx.flags |= CTOK_M_COLON_SEEN; + ct->ctx.state = STATE_C_ACCEPT_ARGS; + return; +/* + * Well, it doesn't look like he is going to be specifying any arguments, so we + * just go and decode the command. + */ + default: + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.flags &= ~CTOK_M_STATUS_PASSED; + return; + }/* End IF */ + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = NO; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + + }/* End Switch */ +/* + * Here to parse the major commands (i.e., ones that have no lead-in) + */ + case STATE_C_MAINCOMMANDS: + switch(ct->input_byte){ +/* + * Space is a nop so that it can be used to make macros more readable + */ + case ' ': + ct->ctx.state = STATE_C_INITIALSTATE; + return; +/* + * Carriage Return is a nop so that it can be used in a macro to avoid + * extremely long lines wrapping. + */ + case '\n': + ct->ctx.state = STATE_C_MAINCOMMANDS; + return; +/* + * Here on an escape. First of all, we have to remember that we have seen an + * escape since two in a row will terminate the command. Also, escape has the + * effect of blocking arguments from commands i.e. 2L will move two lines, but + * 2$L will only move one since the escape eats the argument. + */ + case ESCAPE: + ct->ctx.iarg1_flag = ct->ctx.iarg2_flag = NO; + ct->ctx.state = STATE_C_ESCAPESEEN; + return; +/* + * Here on an Asterisk command. This causes the last double-escaped command + * sequence to be saved in the named q-register. + */ + case '*': + if(parse_any_arguments(ct,"*")) return; + ct->ctx.state = STATE_C_SAVECOMMAND; + return; +/* + * Here on the @ command. This says to use user specified delimeters for + * strings, rather than just terminating with escape. + */ + case '@': + ct->ctx.flags |= CTOK_M_ATSIGN_SEEN; + return; +/* + * Here on the : command. This is just a flag to many commands to tell them + * to work in a slightly different way. The most common usage is to mean that + * the command should return a value which says whether it worked or not. + */ + case ':': + ct->ctx.flags |= CTOK_M_COLON_SEEN; + return; +/* + * Here on the [ command. This causes the specified Q register to be pushed + * onto the Q register pushdown stack. + */ + case '[': + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + ct->ctx.state = STATE_C_PUSH_QREGISTER; + return; + +/* + * Here on the ] command. This causes a Q register to be popped off of the + * Q register pushdown stack, and replaces the specified Q register. + */ + case ']': + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + ct->ctx.state = STATE_C_POP_QREGISTER; + return; +/* + * The B command moves backwards by lines. It is strictly a Video TECO + * enhancement. In normal TECO, the B command returns the address of the + * begining of the buffer, i.e. 0. + */ + case 'B': case 'b': + if(parse_more_than_one_arg(ct,"B")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_RLINE; + return; +/* + * Here on the C command. The C command is a relative move command, i.e., the + * argument says how many spaces from the current position to move. + */ + case 'C': case 'c': + if(parse_more_than_one_arg(ct,"C")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_CHAR; + return; +/* + * The D command deletes the specified number of characters in the indicated + * direction. + */ + case 'D': case 'd': + if(parse_more_than_one_arg(ct,"D")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_DELETE; + return; +/* + * Here on an E command. This is simply the lead-in to any one of the many + * E commands. + */ + case 'E': case 'e': + ct->ctx.state = STATE_C_ECOMMAND; + return; +/* + * Here on an F command. This is a lead-in to one of the F? commands. + */ + case 'F': case 'f': + ct->ctx.state = STATE_C_FCOMMAND; + return; +/* + * The G command copies the contents of the specified q-register into the + * edit buffer at the current position. + */ + case 'G': case 'g': + if(parse_any_arguments(ct,"G")) return; + ct->ctx.state = STATE_C_GQREGISTER; + return; +/* + * The I command inserts characters until an escape is input. If this is + * entered with the tab command, not only do we enter insert mode, we also + * insert the tab itself. + */ + case '\t': + if(ct->ctx.iarg1_flag == NO) ct->flags &= ~TOK_M_EAT_TOKEN; + + + case 'I': case 'i': + if(parse_more_than_one_arg(ct,"I")) return; + + if(ct->ctx.iarg1_flag == YES){ + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_INSERT; + return; + }/* End IF */ + + if(ct->ctx.flags & CTOK_M_ATSIGN_SEEN){ + ct->ctx.state = STATE_C_ATINSERT; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_INSERT; + return; +/* + * Here on the J command. The J command jumps to an absolute position in the + * buffer. + */ + case 'J': case 'j': + if(parse_more_than_one_arg(ct,"J")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_JUMP; + return; +/* + * The K command acts just like the L command except that it deletes instead + * of moving over. Also, it is legal to specify an a,b range to K + */ + case 'K': case 'k': + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_KILL; + return; +/* + * The L command moves over the specified number of new-line characters. An + * argument of zero means get to the begining of the current line. + */ + case 'L': case 'l': + if(parse_more_than_one_arg(ct,"L")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_LINE; + return; +/* + * The M command executes the contents of the specified Q register as a macro. + */ + case 'M': case 'm': + ct->ctx.state = STATE_C_MQREGISTER; + return; +/* + * The N command will search for the specified string across multiple edit + * buffers. + */ + case 'N': case 'n': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_NSEARCH; + return; +/* + * The O command goes to the specified label + */ + case 'O': case 'o': + if(parse_any_arguments(ct,"O")) return; + ct->ctx.state = STATE_C_GOTO; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_GOTO_BEGIN; + return; +/* + * The P command selects the next window + */ + case 'P': case 'p': + if(parse_more_than_one_arg(ct,"P")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_NEXT_WINDOW; + return; +/* + * The R command is exactly like the C command, except that the direction + * is reversed. + */ + case 'R': case 'r': + if(parse_more_than_one_arg(ct,"R")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_RCHAR; + return; +/* + * The S command will search for the specified string + */ + case 'S': case 's': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_SEARCH; + return; +/* + * The ^L command is temporarily used to redraw the screen + */ + case '\f': + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_REDRAW_SCREEN; + return; +/* + * The U command loads the argument into the specified Q register + */ + case 'U': case 'u': + if(parse_more_than_one_arg(ct,"U")) return; + ct->ctx.state = STATE_C_UQREGISTER; + return; +/* + * The V command deletes words. This is a Vido TECO enhancement. In normal + * TECO, the V command was equivalent to 0TT + */ + case 'V': case 'v': + if(parse_more_than_one_arg(ct,"V")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_DELWORD; + return; +/* + * The W command moves by words + */ + case 'W': case 'w': + if(parse_more_than_one_arg(ct,"W")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_WORD; + return; +/* + * The X command moves a block of characters from the edit buffer into + * the specified q register. + */ + case 'X': case 'x': + ct->ctx.state = STATE_C_XQREGISTER; + return; +/* + * The Y command deletes words in reverse direction. This is a Video TECO + * enhancement. In classic TECO, the Y command 'yanked' input data into the + * edit buffer. + */ + case 'Y': case 'y': + if(parse_more_than_one_arg(ct,"Y")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_RDELWORD; + return; +/* + * The ^A command allows you to print a message to the message line + */ + case CNTRL_A: + if(parse_any_arguments(ct,"^A")) return; + ct->ctx.state = STATE_C_MESSAGE; + ct->execute_state = EXEC_C_RESET_MESSAGE; + return; +/* + * The < command opens an iteration + */ + case '<': + if(parse_more_than_one_arg(ct,"<")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_ITERATION_BEGIN; + ct->ctx.inest += 1; + ct->execute_state = EXEC_C_ITERATION_BEGIN; + return; +/* + * The > command closes an iteration + */ + case '>': + if(parse_more_than_one_arg(ct,">")) return; + if(ct->ctx.inest == 0){ + error_message("?No Iteration Present"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.state = STATE_C_INITIALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_ITERATION_END; + while((oct = oct->prev_token)){ + if(oct->opcode != TOK_C_ITERATION_BEGIN) continue; + if(oct->ctx.inest != ct->ctx.inest) continue; + ct->ctx.caller_token = oct; + break; + }/* End While */ +/* + * Preserve the arguments so that if the loop gets undone, it will get redone + * the correct number of iterations when the > is typed again. + */ + { + register struct undo_token *ut; + ut = allocate_undo_token(ct); + if(ut == NULL){ + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ut->opcode = UNDO_C_PRESERVEARGS; + ut->iarg1 = oct->ctx.iarg1; + ut->iarg2 = oct->ctx.iarg2; + ut->carg1 = (char *)oct; + + ct->ctx.inest -= 1; + ct->execute_state = EXEC_C_ITERATION_END; + ct = allocate_cmd_token(ct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_COPYTOKEN; + return; + } +/* + * The ; command terminates an iteration if the argument is >= 0. + * If there is no argument supplied, it uses the state of the last + * search operation performed as a value. + */ + case ';': + if(parse_more_than_one_arg(ct,";")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_SEMICOLON; + return; +/* + * The = command prints out the value of the supplied expression. + * A single = prints out in decimal, == prints in octal and === in hex. + */ + case '=': + if(parse_more_than_one_arg(ct,"=")) return; + ct->ctx.state = STATE_C_ONE_EQUALS; + ct->execute_state = EXEC_C_EQUALS; + return; +/* + * The " command is the conditional 'IF' operator. The following commands only + * get executed if the condition is satisfied. + */ + case '"': + if(parse_more_than_one_arg(ct,"\"")) return; + ct->ctx.state = STATE_C_CONDITIONALS; + ct->ctx.cnest += 1; + return; +/* + * The | command provides an else clause to a conditional expression + */ + case '|': + if(parse_any_arguments(ct,"|")) return; + if(ct->ctx.cnest <= 0){ + error_message("?Not in a conditional"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_SKIP_ELSE; + ct->execute_state = EXEC_C_SKIP_ELSE; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; +/* + * The ' command ends a conditional expression. + */ + case '\'': + if(parse_any_arguments(ct,"'")) return; + if(ct->ctx.cnest <= 0){ + error_message("?Not in a conditional"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_INITIALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_CONDITIONAL_END; + ct->ctx.cnest -= 1; + return; +/* + * The Label command allows you to set a tag which can be jumped to with + * the 'O' command. It also provides a way to comment macros. + */ + case '!': + if(parse_any_arguments(ct,"!")) return; + ct->ctx.state = STATE_C_LABEL; + ct->execute_state = EXEC_C_SKIPLABEL; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_LABEL_BEGIN; + return; +/* + * The backslash command with an argument inserts the decimal + * representation of the argument into the buffer at the current position. + */ + case '\\': + if(ct->ctx.iarg2_flag == NO){ + ct->ctx.iarg2 = 10; + ct->flags |= TOK_M_PARSELOAD_IARG2; + }/* End IF */ + + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_BACKSLASH; + return; +/* + * The open-brace command copies the current command string into a special + * Q-register and places the user there so he can edit it. + */ + case '{': + if(parse_any_arguments(ct,"{")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_OPENBRACE; + return; + +/* + * The close-brace command replaces the command string with the string in + * the special Q-register. + */ + case '}': + if(parse_any_arguments(ct,"}")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_CLOSEBRACE; + return; + +/* + * Here on a system which doesn't really do suspend correctly + */ + case CNTRL_Z: + if(suspend_is_okay_flag == YES){ + cmd_suspend(); + }/* End IF */ + ct->ctx.state = STATE_C_INITIALSTATE; + return; + +/* + * Here to toggle trace mode. This inserts information into q-register ? + * to help us track execution. + */ + case '?': + trace_mode_flag = !trace_mode_flag; + screen_message(trace_mode_flag ? "Trace Mode ON" : + "Trace Mode OFF"); + ct->ctx.state = STATE_C_INITIALSTATE; + return; + +/* + * We only get here on an error, since we should never dispatch a command that + * does not have a corresponding case statement. + */ + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?MAINCOMMANDS: unknown command <%c>", + UPCASE((int)ct->input_byte)); + error_message(tmp_message); + return; + }/* End Switch */ +/* + * Here when we have seen the begining of a conditional '"'. Now we have to + * look at the next character to determine what the actual condition test is. + */ + case STATE_C_CONDITIONALS: + switch(ct->input_byte){ + case 'G': case 'g': + case '>': + ct->execute_state = EXEC_C_COND_GT; + break; + case 'L': case 'l': + case 'T': case 't': + case 'S': case 's': + case '<': + ct->execute_state = EXEC_C_COND_LT; + break; + case 'E': case 'e': + case 'F': case 'f': + case 'U': case 'u': + case '=': + ct->execute_state = EXEC_C_COND_EQ; + break; + case 'N': case 'n': + case '!': + ct->execute_state = EXEC_C_COND_NE; + break; + case 'C': case 'c': + ct->execute_state = EXEC_C_COND_SYMBOL; + break; + case 'D': case 'd': + ct->execute_state = EXEC_C_COND_DIGIT; + break; + case 'A': case 'a': + ct->execute_state = EXEC_C_COND_ALPHA; + break; + case 'V': case 'v': + ct->execute_state = EXEC_C_COND_LOWER; + break; + case 'W': case 'w': + ct->execute_state = EXEC_C_COND_UPPER; + break; + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?Unknown conditional command '%c'", + UPCASE((int)ct->input_byte)); + error_message(tmp_message); + return; + }/* End Switch */ + + ct->ctx.state = STATE_C_INITIALSTATE; + return; +/* + * Here to watch for the end of a label + */ + case STATE_C_LABEL: + switch(ct->input_byte){ + case '!': + ct->ctx.state = STATE_C_INITIALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_LABEL_END; + return; + default: + ct->ctx.state = STATE_C_LABEL; + return; + }/* End Switch */ +/* + * Here to eat the characters in a GOTO command + */ + case STATE_C_GOTO: + switch(ct->input_byte){ + case ESCAPE: + ct->ctx.state = STATE_C_INITIALSTATE; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_GOTO_END; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->execute_state = EXEC_C_GOTO; + return; + default: + ct->ctx.state = STATE_C_GOTO; + return; + }/* End Switch */ +/* + * Here if we have seen an escape. If we see another, then he wants us to + * complete the parse and execute any remaining commands. + */ + case STATE_C_ESCAPESEEN: + switch(ct->input_byte){ + case ESCAPE: + if(ct->ctx.inest){ + error_message("?Unterminated Iteration"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + if(ct->ctx.cnest){ + error_message("?Unterminated Conditional"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_FINALSTATE; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->opcode = TOK_C_FINALTOKEN; + return; + + default: + ct->ctx.state = STATE_C_INITIALSTATE; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ +/* + * Here if we have seen an E as a lead-in to one of the E commands. + */ + case STATE_C_ECOMMAND: + switch(ct->input_byte){ +/* + * The EB command creates a new edit buffer and reads the specified file in. + * If the command is given an argument, then this is a shorthand switch to + * the buffer that has that number. + */ + case 'B': case 'b': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + if(ct->ctx.iarg1_flag == YES){ + ct->execute_state = EXEC_C_EDITBUF; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + ct->ctx.state = STATE_C_INITIALSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_EDITBUF; + return; +/* + * The EC command allows you to execute a command, and the output is placed + * into the edit buffer. + */ + case 'C': case 'c': + if(ct->ctx.iarg1_flag == YES){ + ct->execute_state = EXEC_C_ECCOMMAND; + ct->ctx.state = STATE_C_INITIALSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_ECCOMMAND; + return; +/* + * The EF command closes the current edit buffer. If the current buffer + * is modified, the command will fail unless the user specifies an argument + * to the command. + */ + case 'F': case 'f': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->execute_state = EXEC_C_CLOSEBUF; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The EI command allows you to alter immediate execution mode. With no + * argument, execute mode is turned off for the remainder of the current + * command. An argument of 0 turns it off until further notice and an + * argument of 1 turns it back on. + */ + case 'I': case 'i': + if(parse_more_than_one_arg(ct,"EI")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + + if(ct->ctx.iarg1_flag == NO){ + ct->ctx.go_flag = NO; + return; + }/* End IF */ + + ct->execute_state = EXEC_C_SET_IMMEDIATE_MODE; + return; +/* + * The EJ command allows you to load runtime options into TECO. + */ + case 'J': case 'j': + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_SETOPTIONS; + return; + +/* + * The EP command splits the current window in half. If an argument is + * supplied, the command deletes the current window. + */ + case 'P': case 'p': + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_WINDOW_CONTROL; + return; +/* + * The EV command works exactly like the EB command except that the buffer + * which is created is 'readonly'. Thus this command stands for 'VIEW' file. + */ + case 'V': case 'v': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + if(ct->ctx.iarg1_flag == YES){ + ct->execute_state = EXEC_C_VIEWBUF; + ct->ctx.state = STATE_C_INITIALSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_VIEWBUF; + return; +/* + * The EQ command reads a file into the specified Q-register. + */ + case 'Q': case 'q': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + if(parse_any_arguments(ct,"EQ")) return; + ct->ctx.state = STATE_C_EQQREGISTER1; + return; +/* + * The ER command reads the specified file into the current buffer location. + */ + case 'R': case 'r': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_READFILE; + return; +/* + * The ES command causes the screen to scroll + */ + case 'S': case 's': + if(parse_more_than_one_arg(ct,"ES")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_SCROLL; + return; +/* + * The ET command causes the screen to update + */ + case 'T': case 't': + if(parse_any_arguments(ct,"ET")) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_UPDATE_SCREEN; + return; +/* + * The EW command writes out the current buffer + */ + case 'W': case 'w': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_WRITEFILE; + return; +/* + * The EX command causes the editor to exit + */ + case 'X': case 'x': + ct->execute_state = EXEC_C_EXITCOMMAND; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->ctx.go_flag = NO; + return; + + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?Unknown command E%c",UPCASE((int)ct->input_byte)); + error_message(tmp_message); + return; + }/* End Switch */ +/* + * Here if we have seen an F as a lead-in to one of the F commands. + */ + case STATE_C_FCOMMAND: + switch(ct->input_byte){ +/* + * The FD command finds the string and deletes it. + */ + case 'D': case 'd': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_FDCOMMAND; + return; +/* + * The FK command clears the text between the current position and the position + * of the searched for text. The searched for text is left unmodified. + */ + case 'K': case 'k': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_FKCOMMAND; + return; +/* + * The FS command finds the first string and replaces it with the second + */ + case 'S': case 's': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_FSPART1; + return; +/* + * The FT command is a Video TECO extension to read & use a unix style + * tags file. + */ + case 'T': case 't': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->ctx.state = STATE_C_STRING; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_FTAGS; + return; + +/* + * The FR command acts like the FS command except that if the second argument + * is NULL, the string is replaced with the last replace value. + */ + case 'R': case 'r': + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + ct->ctx.flags |= CTOK_M_STATUS_PASSED; + }/* End IF */ + ct->execute_state = EXEC_C_SEARCH; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->execute_state = EXEC_C_FRREPLACE; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ /* mch */ + ct = allocate_cmd_token(ct); + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + + return; +/* + * Here when we have an 'F' command with an unknown second character. This + * makes it an illegal command, and we don't waste any time letting the user + * know about it. + */ + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?Unknown command F%c",UPCASE((int)ct->input_byte)); + error_message(tmp_message); + return; + }/* End Switch */ +/* + * The following state is the return-state from STRING when the first part + * of an FS command has been parsed. + */ + case STATE_C_FSPART1: + ct->ctx.state = STATE_C_FSPART2; + ct->execute_state = EXEC_C_SEARCH; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->execute_state = EXEC_C_FSREPLACE1; + return; + + case STATE_C_FSPART2: + ct->flags |= TOK_M_WORDBOUNDARY; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_FSPART3; + return; + + case STATE_C_FSPART3: + if(ct->ctx.flags & CTOK_M_ATSIGN_SEEN){ + if(ct->input_byte == ct->ctx.delimeter){ + ct->ctx.state = STATE_C_ESCAPESEEN; + ct->execute_state = EXEC_C_FSREPLACE3; + return; + } + + else { + ct->ctx.state = STATE_C_FSPART3; + ct->execute_state = EXEC_C_FSREPLACE2; + return; + + } + } + + if(ct->input_byte == ESCAPE){ + ct->ctx.state = STATE_C_ESCAPESEEN; + ct->execute_state = EXEC_C_FSREPLACE3; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ /* mch */ + ct = allocate_cmd_token(ct); + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; + } + + else { + ct->ctx.state = STATE_C_FSPART3; + ct->execute_state = EXEC_C_FSREPLACE2; + return; + + } + +#ifdef WIMPY_CODER_WHO_HAS_NOT_HANDLED_THIS_YET + case CNTRL_V: + ct->ctx.state = STATE_C_QUOTED_INSERT; + return; +#endif /* WIMPY_CODER_WHO_HAS_NOT_HANDLED_THIS_YET */ + +/* + * This state is the return state from STRING and the TAGS command is + * ready to execute. + */ + case STATE_C_FTAGS: + ct->execute_state = EXEC_C_FTAGS; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; + +/* + * The following state is the return-state from STRING when a search string + * has been completely specified. + */ + case STATE_C_SEARCH: + ct->execute_state = EXEC_C_SEARCH; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is the return-state from STRING when a search string + * has been completely specified for the 'N' search command. + */ + case STATE_C_NSEARCH: + ct->execute_state = EXEC_C_NSEARCH; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is the return-state from STRING when the search part + * of an FD command has been parsed. + */ + case STATE_C_FDCOMMAND: + ct->execute_state = EXEC_C_SEARCH; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.carg = NULL; + ct->execute_state = EXEC_C_FDCOMMAND; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ /* mch */ + ct = allocate_cmd_token(ct); + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is the return-state from STRING when the search part + * of an FK command has been parsed. + */ + case STATE_C_FKCOMMAND: + ct->execute_state = EXEC_C_REMEMBER_DOT; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->execute_state = EXEC_C_SEARCH; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.carg = NULL; + ct->execute_state = EXEC_C_FKCOMMAND; + if(ct->ctx.flags & CTOK_M_STATUS_PASSED){ /* mch */ + ct = allocate_cmd_token(ct); + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the name of the file to + * write. + */ + case STATE_C_WRITEFILE: + ct->execute_state = EXEC_C_WRITEFILE; + ct->ctx.go_flag = NO; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the name of the file to + * read into the current buffer location. + */ + case STATE_C_READFILE: + ct->execute_state = EXEC_C_READFILE; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the name of the file which + * we want to create a buffer for and load. + */ + case STATE_C_EDITBUF: + ct->execute_state = EXEC_C_EDITBUF; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(ct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the name of the file which + * we want to create a readonly buffer for and load. + */ + case STATE_C_VIEWBUF: + ct->execute_state = EXEC_C_VIEWBUF; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(ct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state is a return-state from STRING when we have seen an + * escape terminated string which in this case is the command the user wants + * to execute. + */ + case STATE_C_ECCOMMAND: + ct->execute_state = EXEC_C_ECCOMMAND; + ct->ctx.state = STATE_C_INITIALSTATE; + if(ct->input_byte == ESCAPE) ct->ctx.state = STATE_C_ESCAPESEEN; + return; +/* + * The following state gets entered when the user has typed ^A + * to print out a message. + */ + case STATE_C_MESSAGE: + switch(ct->input_byte){ + case CNTRL_A: + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_OUTPUT_MESSAGE; + return; + default: + ct->execute_state = EXEC_C_MESSAGE; + ct->ctx.state = STATE_C_MESSAGE; + return; + }/* End Switch */ + +/* + * The following state is a return-state from EXPRESSION when we have seen + * what appears to be a first argument. It stashes the argument and tests if + * there is a second argument available (indicated by a comma). + */ + case STATE_C_ARG1: + ct->ctx.iarg1_flag = YES; + ct->execute_state = EXEC_C_STORE1; + switch(ct->input_byte){ + case ',': + ct->ctx.state = STATE_C_EXPRESSION; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_ARG2; + return; + + default: + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ +/* + * Here if the first argument was followed by a comma. This indicates that a + * second argument is being specified. Only certain commands accept a double + * argument. + */ + case STATE_C_ARG2: + ct->ctx.iarg2_flag = YES; + ct->execute_state = EXEC_C_STORE2; + switch(ct->input_byte){ + case ',': + ct->ctx.state = STATE_C_ERRORSTATE; + error_message("?Maximum of two arguments exceeded"); + return; + + default: + ct->ctx.state = STATE_C_MAINCOMMANDS; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ +/* + * The next state is the expression substate. States outside of the expression + * parser call through here so that pnest (the current nesting level of + * parentheses) gets set to zero. Then it calls the expression parser's own + * internal sub-expression state. + */ + case STATE_C_EXPRESSION: + ct->ctx.pnest = 0; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_SUBEXPRESSION; + return; +/* + * This is the sub-expression state. It gets called from within the expression + * parser when we want to recursively parse an expression. This lets us handle + * precedence and parenthesis correctly. + */ + case STATE_C_SUBEXPRESSION: + switch(ct->input_byte){ +/* + * When we see an open parenthesis, we want to recursively call the + * subexpression state to parse the contents of the parenthesis. Since the open + * parenthesis always occurs in place of an operand, we set that to be the next + * state to call. + */ + case '(': + ct->ctx.pnest += 1; + ct->ctx.state = STATE_C_OPERAND; + + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_OPERATOR; + + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_OPERATOR; + return; +/* + * If we see a minus sign here, it is a unary minus. The difference between + * the unary minus and the normal minus is one of precedence. The unary minus + * is very high precedence. + */ + case '-': + ct->ctx.state = STATE_C_MINUSSEEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_UMINUS; + return; +/* + * Here on a leading % command. This just means he is going to default to + * incrementing the queue register by one. + */ + case '%': + ct->ctx.tmpval = 1; + ct->flags |= TOK_M_PARSELOAD_TMPVAL; +/* ;;; the following line shouldn't be required, but is. A bug. */ + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.state = STATE_C_PERCENT_OPERAND; + return; +/* + * Well, not much exciting going on here, so we just call the normal operand + * state to parse the current token. + */ + default: + ct->ctx.state = STATE_C_OPERAND; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_OPERATOR; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ +/* + * Here if we saw a unary minus. The reason for calling this state at all is to + * catch constructs like -L which really implies -1L. In a case like this, + * the OPERAND state will not see anything it understands, and will return. + * So as to make the defaulting work correctly, we catch that case here and + * default so that we get our -1. + */ + case STATE_C_MINUSSEEN: + switch(ct->input_byte){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'Q': case 'q': case '%': + case '.': +#ifdef CLASSIC_B_BEHAVIOR + case 'B': case 'b': +#endif + case 'Z': case 'z': + case '(': + case '^': + ct->ctx.state = STATE_C_OPERAND; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + + default: + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = 1; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch */ +/* + * Here on the return from the OPERAND state. Whatever it parsed, we want to + * make it minus. + */ + case STATE_C_UMINUS: + ct->execute_state = EXEC_C_UMINUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; +/* + * Here when we are expecting an operator like + = * /. Note that ')' also gets + * handled here since it always appears in the place an operator would be + * expected to appear. + */ + case STATE_C_OPERATOR: + switch(ct->input_byte){ +/* + * Here on an a-b case. Note that since minus is a low precedence operator, + * we stash the temporary value, and call subexpression to parse the rest + * of the expression. In that way, if the next operator was * or /, it will + * get processed first. + */ + case '-': + ct->execute_state = EXEC_C_STORE2; + ct->ctx.state = STATE_C_OPERAND; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_MINUS; + return; +/* + * Here on an a+b case. This is similar to the case above in that we want to + * just stash the first value, and then recursively call the parser to handle + * the rest of the expression first. Note that this causes a+b+c to actually + * get handled as a+(b+c) which is a little weird, but works out ok. + */ + case '+': + ct->execute_state = EXEC_C_STORE2; + ct->ctx.state = STATE_C_OPERAND; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_PLUS; + return; +/* + * Here on the a*b case. Unlike the above two cases, we want to immediately + * handle this case since it is the highest precedence of the four operators. + */ + case '*': + ct->execute_state = EXEC_C_STORE1; + ct->ctx.state = STATE_C_OPERAND; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_TIMES; + return; +/* + * Here on the a/b case. This is just like the multiply state + */ + case '/': + ct->execute_state = EXEC_C_STORE1; + ct->ctx.state = STATE_C_OPERAND; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_DIVIDE; + return; + +/* + * Here on the n%q case. This just collapses to the value of the Q-register + * after it has been incremented by 'n' + */ + case '%': + ct->ctx.state = STATE_C_PERCENT_OPERAND; + return; +/* + * Here on the 'A' command which returns a value + */ + case 'A': case 'a': + ct->execute_state = EXEC_C_ACOMMAND; + ct->ctx.state = STATE_C_OPERATOR; + return; +/* + * Here on a close parenthesis. This will force the end of a subexpression + * parse even though it would not normally have terminated yet. Note the check + * for the guy typing too many of these. + */ + case ')': + if(ct->ctx.pnest == 0){ + error_message("?No Matching Open Parenthesis"); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.pnest -= 1; + ct->ctx.state = STATE_C_RETURN; + return; +/* + * If this is not an operator character, it must be the end of the expression. + * In this case, we must be back at the zero level for parenthesis nesting. + */ + default: + if(ct->ctx.pnest > 0){ + sprintf(tmp_message,"?Missing %d Close Parenthesis", + ct->ctx.pnest); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch */ +/* + * Here when we have the 'b' part of an a+b expression. This happens either + * when the end of the expression has been completed, or a parenthesis has + * forced a temporary end as in: (a+b) + */ + case STATE_C_PLUS: + switch(ct->input_byte){ + case '-': + case '+': + case ')': + ct->execute_state = EXEC_C_PLUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; + default: + ct->ctx.state = STATE_C_OPERATOR; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_DELAYED_PLUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ + + case STATE_C_DELAYED_PLUS: + ct->execute_state = EXEC_C_PLUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; + +/* + * Here when we have the 'b' part of an a-b expression. This happens either + * when the end of the expression has been completed, or a parenthesis has + * forced a temporary end as in: (a-b) + */ + case STATE_C_MINUS: + switch(ct->input_byte){ + case '-': + case '+': + case ')': + ct->execute_state = EXEC_C_MINUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; + default: + ct->ctx.state = STATE_C_OPERATOR; + ct->flags &= ~TOK_M_EAT_TOKEN; + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_DELAYED_MINUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End Switch */ + + case STATE_C_DELAYED_MINUS: + ct->execute_state = EXEC_C_MINUS; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; + +/* + * Here when we have both arguments to a multiply. Because multiply is a higher + * precedence operator than +-, it happens immediately unless parenthesis get + * involved. + */ + case STATE_C_TIMES: + ct->execute_state = EXEC_C_TIMES; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; +/* + * Here when we have both arguments to a divide. Because division is a higher + * precedence operator than -+, it generally happens immediately. Note that we + * check to be sure that the divisor is non-zero. + */ + case STATE_C_DIVIDE: + ct->execute_state = EXEC_C_DIVIDE; + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_OPERATOR; + return; +/* + * Here to parse any legal TECO operand. These would include numbers and + * commands that return values. Since open parenthesis occur in the same place + * as operands, we also catch them here. + */ + case STATE_C_OPERAND: + switch(ct->input_byte){ +/* + * If we see a numeric digit, call a substate which will continue eating bytes + * until a non-digit is seen. + */ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ct->ctx.state = STATE_C_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - '0'; + return; +/* + * Here if he wants to input in a different radix + */ + case '^': + ct->ctx.state = STATE_C_RADIX; + ct->ctx.tmpval = 0; + return; +/* + * Here if he is specifying the numeric value of a q-register. We have to go + * to another state to determine which q-register he wants to access. + */ + case 'Q': case 'q': + ct->ctx.state = STATE_C_QOPERAND; + return; +/* + * Here when he has specified . This will return as a value the current + * position in the edit buffer. + */ + case '.': + ct->ctx.state = STATE_C_RETURN; + ct->execute_state = EXEC_C_DOTVALUE; + return; +#ifdef CLASSIC_B_BEHAVIOR +/* + * B is a special operand which returns the address of the first character + * in the buffer. This is currently always 0, but may change if some really + * obscure features get put in one of these days. + */ + case 'B': case 'b': + ct->ctx.state = STATE_C_RETURN; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = 0; + return; +#endif +/* + * Z is a special operand which is the number of characters currently in the + * edit buffer. + */ + case 'Z': case 'z': + ct->ctx.state = STATE_C_RETURN; + ct->execute_state = EXEC_C_ZEEVALUE; + return; +/* + * BACKSLASH with no argument actually looks in the buffer at the current + * position and eats numeric digits until it hits a non-digit. It then + * returns the number as a value. + */ + case '\\': + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_BACKSLASH; + ct->ctx.tmpval = 10; + ct->execute_state = EXEC_C_STOREVAL; + ct = allocate_cmd_token(ct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->execute_state = EXEC_C_STORE1; + return; +/* + * If we see a parenthesis in place of an operand, we want to parse it as + * a complete expression of its own until a matching close parenthesis. + */ + case '(': + ct->ctx.state = STATE_C_SUBEXPRESSION; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; +/* + * We really should not get here if the guy is typing good syntax, so we + * tell him it is junk and we don't allow it. + */ + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message, + "?Unexpected character <%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + }/* End Switch */ +/* + * The following is a substate to parse a number. It will continue until + * it sees a non-digit, and then return to the calling state. + */ + case STATE_C_NUMBER_SUBSTATE: + switch(ct->input_byte){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ct->ctx.state = STATE_C_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->ctx.tmpval * 10 + ct->input_byte - '0'; + return; + default: + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch*/ +/* + * The following is a substate to parse a hex number. It will continue until + * it sees a non hex digit, and then return to the calling state. + */ + case STATE_C_HEX_NUMBER_SUBSTATE: + switch(ct->input_byte){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval *= 16; + ct->ctx.tmpval += ct->input_byte - '0'; + return; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval *= 16; + ct->ctx.tmpval += ct->input_byte - 'a' + 10; + return; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval *= 16; + ct->ctx.tmpval += ct->input_byte - 'A' + 10; + return; + default: + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch*/ +/* + * The following is a substate to parse a octal number. It will continue until + * it sees a non octal digit, and then return to the calling state. + */ + case STATE_C_OCTAL_NUMBER_SUBSTATE: + switch(ct->input_byte){ + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + ct->ctx.state = STATE_C_OCTAL_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval *= 8; + ct->ctx.tmpval += ct->input_byte - '0'; + return; + case '8': case '9': + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message,"?Illegal octal digit <%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + default: + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End Switch*/ +/* + * The following state gets the value of the specified q-register + */ + case STATE_C_QOPERAND: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_RETURN; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_QVALUE; + return; +/* + * This is just like QOPERAND except the execute state is different + */ + case STATE_C_PERCENT_OPERAND: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_OPERATOR; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_PERCENT_VALUE; + return; +/* + * Here on a input radix character + */ + case STATE_C_RADIX: + switch(ct->input_byte){ + case 'X': case 'x': + ct->ctx.state = STATE_C_HEX_NUMBER; + ct->execute_state = EXEC_C_STOREVAL; + return; + case 'O': case 'o': + ct->ctx.state = STATE_C_OCTAL_NUMBER; + ct->execute_state = EXEC_C_STOREVAL; + return; + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message,"?Unknown radix ^<%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + }/* End Switch */ + + case STATE_C_HEX_NUMBER: + switch(ct->input_byte){ + case '\\': + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_BACKSLASH; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = 16; + return; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - '0'; + return; + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - 'a' + 10; + return; + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': + ct->ctx.state = STATE_C_HEX_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - 'A' + 10; + return; + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message,"?Illegal hex digit <%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + }/* End Switch */ + + case STATE_C_OCTAL_NUMBER: + switch(ct->input_byte){ + case '\\': + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_BACKSLASH; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = 8; + return; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': + ct->ctx.state = STATE_C_OCTAL_NUMBER_SUBSTATE; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte - '0'; + return; + default: + ct->ctx.state = STATE_C_ERRORSTATE; + sprintf(tmp_message,"?Illegal octal digit <%c> in operand", + ct->input_byte); + error_message(tmp_message); + return; + }/* End Switch*/ + +/* + * The next state is the string substate. Outside states call in through here + * which does the initial tec_alloc of the string storage. Strings are limited + * to PARSER_STRING_MAX bytes at this time. + */ + case STATE_C_STRING: + if((ct->ctx.flags & CTOK_M_ATSIGN_SEEN) == 0){ + ct->ctx.delimeter = ESCAPE; + ct->flags &= ~TOK_M_EAT_TOKEN; + } + else { + ct->ctx.delimeter = ct->input_byte; + } + ct->ctx.state = STATE_C_STRING1; + return; + + case STATE_C_STRING1: +/* + * First, check if this is the termination character. This would normally be + * an escape, but could be another character if the user used the @ form. + */ + if( ( (ct->input_byte == ESCAPE) && + (!(ct->ctx.flags & CTOK_M_ATSIGN_SEEN)) + ) || + ( (ct->ctx.flags & CTOK_M_ATSIGN_SEEN) && + (ct->input_byte == ct->ctx.delimeter) && + (ct->ctx.carg != NULL) + ) + ){ + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_RETURN; + return; + }/* End IF */ + +/* + * Here when we see a normal character inside of the string. Note that we + * have to do the undo stuff to manage the string since it gets stored in + * a regular string array rather than being spread through the command + * tokens. + */ + {/* Local Block */ + register char *cp; + register struct undo_token *ut; +/* + * Get the address of the string + */ + cp = ct->ctx.carg; +/* + * If there is no string allocated yet, then we have to allocate one. + * Also, if an @ was seen, then record the delimeter character. + */ + if(cp == NULL){ + ct->ctx.carg = cp = tec_alloc(TYPE_C_CBUFF,PARSER_STRING_MAX); + if(cp == NULL){ + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + *cp = '\0'; + ut = allocate_undo_token(uct); + if(ut == NULL){ + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ut->opcode = UNDO_C_MEMFREE; + ut->carg1 = cp; + }/* End IF */ +/* + * Each time we append a character to the string, we also have to allocate + * an undo token which will shorten the string if the input character gets + * rubbed out. + */ + ut = allocate_undo_token(uct); + if(ut == NULL){ + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ut->opcode = UNDO_C_SHORTEN_STRING; + ut->carg1 = cp; + ut->iarg1 = 0; + while(*cp){ + ut->iarg1 += 1; + cp++; + }/* End While */ + if(ut->iarg1 >= (PARSER_STRING_MAX-1)){ + sprintf(tmp_message,"?Exceeded maximum string length of %d", + PARSER_STRING_MAX-1); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ +/* + * Finally, we append the input byte to the string + */ + *cp++ = ct->input_byte; + *cp++ = '\0'; + ct->ctx.state = STATE_C_STRING1; + return; + }/* End Local Block */ +/* + * The following state gets the Q register that will be used with the + * G command. + */ + case STATE_C_GQREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_GQREGISTER; + return; +/* + * The following state gets the Q register that will be used with the + * M command. + */ + case STATE_C_MQREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_MQREGISTER; + return; +/* + * The following two states implement the EQ command which reads a file into + * the specified Q-register. + */ + case STATE_C_EQQREGISTER1: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.tmpval = ct->input_byte; + ct->ctx.state = STATE_C_STRING; + + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.caller_token = oct; + ct->ctx.return_state = STATE_C_EQQREGISTER2; + return; + case STATE_C_EQQREGISTER2: + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_EQQREGISTER; + if(ct->ctx.flags & CTOK_M_COLON_SEEN){ + oct = ct; + ct = allocate_cmd_token(oct); + if(ct == NULL){ + oct->ctx.state = STATE_C_ERRORSTATE; + return; + }/* End IF */ + ct->ctx.iarg1_flag = YES; ct->ctx.iarg2_flag = NO; + ct->execute_state = EXEC_C_STORE1; + }/* End IF */ + return; +/* + * The following state gets the Q register that will be loaded with the + * U command. + */ + case STATE_C_UQREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_UQREGISTER; + return; +/* + * The following state gets the Q register that will be loaded with the + * X command. + */ + case STATE_C_XQREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_XQREGISTER; + return; +/* + * The following state gets the Q register that will be loaded with the + * previous command line in response to the '*' command. + */ + case STATE_C_SAVECOMMAND: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_SAVECOMMAND; + return; +/* + * The following state gets the Q register which will be pushed onto the + * Q register stack. + */ + case STATE_C_PUSH_QREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_PUSH_QREGISTER; + return; +/* + * The following state gets the Q register which will be popped from the + * Q register stack. + */ + case STATE_C_POP_QREGISTER: + if(!parse_check_qname(ct,ct->input_byte)) return; + ct->ctx.state = STATE_C_INITIALSTATE; + ct->q_register = ct->input_byte; + ct->execute_state = EXEC_C_POP_QREGISTER; + return; + +/* + * Here to insert characters. The insert state continues until an escape + * is seen. Escapes can also be quoted if they are to be inserted into the + * buffer. If there are many escapes to be inserted, it may be easier to use + * the @ insert command... + */ + case STATE_C_INSERT: + switch(ct->input_byte){ +/* + * Here when we see a non-quoted escape. This terminates the insert. + */ + case ESCAPE: + ct->flags &= ~TOK_M_EAT_TOKEN; + ct->ctx.state = STATE_C_INITIALSTATE; + return; +/* + * Here to enter a quote character. This lets you insert characters that would + * normally be handled otherwise. ESCAPE is a good example of this. + */ + case CNTRL_V: + ct->ctx.state = STATE_C_QUOTED_INSERT; + return; +/* + * Here to handle a normal character. It will simply be inserted into the + * edit buffer. + */ + default: + ct->ctx.state = STATE_C_INSERT; + ct->execute_state = EXEC_C_INSERT; + return; + }/* End Switch */ +/* + * Here to insert a character directly following the quote character. If + * this is a special character such as an escape, it will be inserted and + * will not terminate the insert command. + */ + case STATE_C_QUOTED_INSERT: + ct->ctx.state = STATE_C_INSERT; + ct->execute_state = EXEC_C_INSERT; + return; + +/* + * Here for the alternate form of insert, @I/text/ + */ + case STATE_C_ATINSERT: + ct->ctx.tmpval = ct->input_byte; + ct->execute_state = EXEC_C_STOREVAL; + ct->ctx.state = STATE_C_ATINSERT_PART2; + return; + + case STATE_C_ATINSERT_PART2: + if(ct->input_byte == ct->ctx.tmpval){ + ct->ctx.state = STATE_C_INITIALSTATE; + return; + }/* End IF */ + + ct->ctx.state = STATE_C_ATINSERT_PART2; + ct->execute_state = EXEC_C_INSERT; + return; +/* + * Here after seeing an equals command. This lets us test for two or three + * in a row which output in octal and hex respectively. + */ + case STATE_C_ONE_EQUALS: + if(ct->input_byte != '='){ + ct->ctx.state = STATE_C_INITIALSTATE; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End IF */ + ct->ctx.state = STATE_C_TWO_EQUALS; + ct->execute_state = EXEC_C_TWO_EQUALS; + return; + case STATE_C_TWO_EQUALS: + if(ct->input_byte != '='){ + ct->ctx.state = STATE_C_INITIALSTATE; + ct->flags &= ~TOK_M_EAT_TOKEN; + return; + }/* End IF */ + ct->ctx.state = STATE_C_INITIALSTATE; + ct->execute_state = EXEC_C_THREE_EQUALS; + return; + +/* + * Here when we have seen a vertical bar. We have to put a mark here so + * that if the condition is not met, the else clause can be found. + */ + case STATE_C_SKIP_ELSE: + ct->opcode = TOK_C_CONDITIONAL_ELSE; + ct->ctx.state = STATE_C_INITIALSTATE; + return; + + case STATE_C_BACKSLASH: + ct->ctx.state = STATE_C_RETURN; + ct->execute_state = EXEC_C_BACKSLASHARG; + return; + + default: + sprintf(tmp_message,"?Dispatched unknown state %d",ct->ctx.state); + error_message(tmp_message); + return; + }/* End Switch */ + +}/* End Routine */ + + + +/* PARSE_CHECK_QNAME - Check that the specified Q-register name is ok + * + * Function: + * + * This routine is called when a parse state wants to verify that the + * specified Q-register name is syntactically correct. It does not + * verify that the Q-register exists, because the execute phase may just + * not have got around to that yet. + */ +int +parse_check_qname(ct,name) +register struct cmd_token *ct; +register char name; +{ +char tmp_message[LINE_BUFFER_SIZE]; + + PREAMBLE(); + + switch(name){ + case '*': + case '_': + case '-': + case '@': + case '?': + return(SUCCESS); + }/* End Switch */ + + if(isalnum((int)name)) return(SUCCESS); + + sprintf(tmp_message,"?Illegal Q-register Name <%c>",name); + error_message(tmp_message); + ct->ctx.state = STATE_C_ERRORSTATE; + return(FAIL); + +}/* End Routine */ diff --git a/tecterm.c b/tecterm.c new file mode 100644 index 0000000..23b506f --- /dev/null +++ b/tecterm.c @@ -0,0 +1,1579 @@ +char *tecterm_c_version = "tecterm.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:21 $ + * $Source: /cvsroot/videoteco/videoteco/tecterm.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecterm.c + * Terminal Escape Sequence Subroutines (termcap/terminfo et all) + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + +#ifdef TERMINFO +#include +#include +#endif + +/* + * Global Storage is defined here for lack of a better place. + */ + +/* + * Global Variables + */ + extern int curx; + extern int cury; + extern int term_columns; + extern int term_lines; + char scr_outbuf[SCREEN_OUTPUT_BUFFER_SIZE]; + char *scr_outbuf_ptr; + int scr_outbuf_left; + char insert_delete_line_capability; + + void term_insert_line(); + void term_delete_line(); + void term_puts(); + int term_putc(int); + void term_flush(); + +#ifdef TERMCAP + char *tgetstr(); +#endif + +#ifdef TERMCAP + int termcap_co; + int termcap_li; + int termcap_sg; + char *termcap_cm; + char *termcap_ti; + char *termcap_te; + char *termcap_pc; + char *termcap_so; + char *termcap_se; + char *termcap_cd; + char *termcap_ce; + char *termcap_cs; + char *termcap_sf; + char *termcap_sr; + char *termcap_al; + char *termcap_dl; + char *termcap_AL_arg; + char *termcap_DL_arg; +#endif + +#ifdef TERMINFO + int terminfo_co; + int terminfo_li; + int terminfo_magic_cookie_glitch; +#endif + +/* + * External Routines, etc. + */ + + extern int term_speed; + extern char teco_startup; + extern int tty_input_chan; + extern int tty_output_chan; + +/* + * The following array gives the number of tens of milliseconds per + * character for each speed as returned by gtty. Thus since 300 + * baud returns a 7, there are 33.3 milliseconds per char at 300 baud. + */ +#ifdef TERMCAP +static +short tmspc10[] = { + 0, 2000, 1333, 909, 743, 666, 500, 333, 166, 83, 55, 41, 20, 10 +}; +#endif + +/* TERM_INIT - Initialize the terminal for cursor operations + * + * Function: + * + * This entry point gives the terminal package a chance to initialize + * the terminal for cursor type operations. + */ +void +term_init() +{ +/* + * Initialize our output buffer. + */ + scr_outbuf_ptr = scr_outbuf; + scr_outbuf_left = SCREEN_OUTPUT_BUFFER_SIZE; + +#ifdef TERMCAP +/* + * "TI - String to begin programs that use cm" + */ + term_puts(termcap_ti,1); + +/* + * Set a fast flag which says whether or not terminal is capable of insert and + * delete line functions. + */ + insert_delete_line_capability = 1; + + if(termcap_dl == NULL && termcap_DL_arg == NULL){ + insert_delete_line_capability = 0; + }/* End IF */ + + if(termcap_al == NULL && termcap_AL_arg == NULL){ + insert_delete_line_capability = 0; + }/* End IF */ + + if(termcap_cs && termcap_sf && termcap_sr){ + insert_delete_line_capability = 1; + }/* End IF */ +#endif + +#ifdef TERMINFO +/* + * Initialize terminfo package + */ + setupterm((char *)0, 1, (int*)0); + + insert_delete_line_capability = 1; + + if(parm_insert_line == NULL && insert_line == NULL){ + insert_delete_line_capability = 0; + }/* End IF */ + + if(parm_delete_line == NULL && delete_line == NULL){ + insert_delete_line_capability = 0; + }/* End IF */ + + if(change_scroll_region && scroll_forward && scroll_reverse){ + insert_delete_line_capability = 1; + }/* End IF */ + + terminfo_magic_cookie_glitch = magic_cookie_glitch > 0 ? + magic_cookie_glitch : 0; + +#endif + +}/* End Routine */ + + + +/* TERM_FINISH - Here when we are exiting the editor + * + * Function: + * + * This routine is called when TECO is being exited to give us a chance + * to cleanly leave the screen package. We put the cursor at the bottom + * of the screen, send any termination escape sequences that are required + * and flush it all out. + */ +void +term_finish() +{ + +/* + * We want to leave the cursor at the bottom of the screen + */ + term_goto(0,term_lines-1); + +#ifdef TERMCAP + +/* + * Output any termination escape sequences + */ + term_puts(termcap_te,1); + +#endif + +/* + * Now flush that all out + */ + term_flush(); + +}/* End Routine */ + + + +/* TERM_STANDOUT - Set standout mode on the terminal + * + * Function: + * + * This routine is called to set the highlighting standout + * mode. All characters output from now on will typically + * be reverse video, or whatever attribute the terminal uses. + */ +void +term_standout() +{ + +#ifdef TERMCAP + + if(!termcap_sg) term_puts(termcap_so,1); + +#endif + +#ifdef TERMINFO + + if(terminfo_magic_cookie_glitch <= 0){ + tputs(enter_standout_mode,1,term_putc); + } + +#endif + +}/* End Routine */ + +/* TERM_STANDEND - Clear standout mode on the terminal + * + * Function: + * + * This routine is called to reset the highlighting standout + * mode. All characters output from now on will be in the + * normal rendition mode. + */ +void +term_standend() +{ + +#ifdef TERMCAP + + if(!termcap_sg) term_puts(termcap_se,1); + +#endif + +#ifdef TERMINFO + + if(terminfo_magic_cookie_glitch <= 0){ + tputs(exit_standout_mode,1,term_putc); + } + +#endif + +}/* End Routine */ + +/* TERM_CLRTOEOL - Clear to End Of Line + * + * Function: + * + * This routine sends the ESCape sequence to clear all characters + * from the present position until the end of the line. + */ +void +term_clrtoeol() +{ +#ifdef TERMCAP + + term_puts(termcap_ce,1); + +#endif + +#ifdef TERMINFO + + tputs(clr_eol,1,term_putc); + +#endif + +}/* End Routine */ + +/* TERM_CLRTOBOT - Clear to End Of Screen + * + * Function: + * + * This routine sends the ESCape sequence to clear all characters + * from the present position until the end of the terminal screen. + */ +void +term_clrtobot() +{ + +#ifdef TERMCAP + + term_puts(termcap_cd,1); + + +#endif + +#ifdef TERMINFO + + tputs(clr_eos,1,term_putc); + +#endif + +}/* End Routine */ + + + +/* TERM_INSERT_LINE - Perform an insert-line terminal function + * + * Function: + * + * This routine is called to output the insert-line escape sequence. + * It checks to see what insert-line capabilities the terminal has, + * and tries to use the optimum one. + */ +void +term_insert_line(position,count) +int position; +register int count; +{ +/* + * Test whether the terminal supports an insert line escape sequence which + * takes an argument + */ +#ifdef TERMCAP + if(count > 1 && termcap_AL_arg){ + term_goto(0,position); + term_putnum(termcap_AL_arg,count,term_lines-position); + return; + }/* End IF */ +#endif + +#ifdef TERMINFO + if(count > 1 && parm_insert_line){ + term_goto(0,position); + tputs( + tparm(parm_insert_line,count), + term_lines-position, + term_putc + ); + return; + }/* End IF */ +#endif + +/* + * Ok, here if either the terminal does not support insert line with an + * argument, or if count happens to be 1 + */ +#ifdef TERMCAP + if(termcap_al){ + term_goto(0,position); + while(count--){ + term_puts(termcap_al,term_lines-position); + }/* End While */ + return; + }/* End IF */ +#endif + +#ifdef TERMINFO + if(insert_line){ + term_goto(0,position); + while(count--){ + tputs(insert_line,term_lines-position,term_putc); + }/* End While */ + return; + }/* End IF */ +#endif + + +/* + * Hmm, termcap_al must be undefined. That must mean that cs is defined and + * that we have to do this by using scroll regions. + */ + if(position) term_scroll_region(position,term_lines-1); + if(curx != 0 || cury != position) term_goto(0,position); + while(count--){ + +#ifdef TERMCAP + term_puts(termcap_sr,term_lines-position); +#endif + +#ifdef TERMINFO + tputs(scroll_reverse,term_lines-position,term_putc); +#endif + + }/* End While */ + if(position) term_scroll_region(0,term_lines-1); + +#ifdef TERMCAP + if(curx != 0 || cury != position) term_goto(0,term_lines-1); +#endif +#ifdef TERMINFO + curx = cury = -1; /* Unsure of present position... */ +#endif + +}/* End Routine */ + + + +/* TERM_DELETE_LINE - Perform an delete-line terminal function + * + * Function: + * + * This routine is called to output the delete-line escape sequence. + * It checks to see what delete-line capabilities the terminal has, + * and tries to use the optimum one. + */ +void +term_delete_line(position,count) +int position; +register int count; +{ + term_goto(0,position); + +/* + * Test whether the terminal supports an delete line escape sequence which + * takes an argument + */ +#ifdef TERMCAP + if(count > 1 && termcap_DL_arg){ + term_putnum(termcap_DL_arg,count,term_lines-position); + return; + }/* End IF */ +#endif + +#ifdef TERMINFO + if(count > 1 && parm_delete_line){ + term_goto(0,position); + tputs( + tparm(parm_delete_line,count), + term_lines-position, + term_putc + ); + return; + }/* End IF */ +#endif + +/* + * Ok, here if either the terminal does not support delete line with an + * argument, or if count happens to be 1 + */ +#ifdef TERMCAP + if(termcap_dl){ + while(count--){ + term_puts(termcap_dl,term_lines-position); + }/* End While */ + return; + }/* End IF */ +#endif + +#ifdef TERMINFO + if(delete_line){ + term_goto(0,position); + while(count--){ + tputs(delete_line,term_lines-position,term_putc); + }/* End While */ + return; + }/* End IF */ +#endif + +/* + * Hmm, termcap_dl must be undefined. That must mean that cs is defined and + * that we have to do this by using scroll regions. + */ + if(position) term_scroll_region(position,term_lines-1); + if(curx != 0 || cury != (term_lines-1)) term_goto(0,term_lines-1); + while(count--){ + +#ifdef TERMCAP + term_puts(termcap_sf,term_lines-position); +#endif + +#ifdef TERMINFO + tputs(scroll_forward,term_lines-position,term_putc); +#endif + + }/* End While */ + if(position) term_scroll_region(0,term_lines-1); + +#ifdef TERMCAP + if(curx != 0 || cury != (term_lines-1)) term_goto(0,term_lines-1); +#endif +#ifdef TERMINFO + curx = cury = -1; /* Unsure of present position... */ +#endif + + +}/* End Routine */ + + + +#ifdef TERMCAP + +/* TERM_PUTNUM - Output a termcap string which takes a single argument + * + * Function: + * + * This routine is called to output a termcap string which has an + * imbedded %d to be replaced with the single argument. + */ +int +term_putnum(termcap_string,argument,affected) +char *termcap_string; +int argument; +int affected; +{ +register char *cp; +register char *dp; +register int c; +static char result[40]; + + cp = termcap_string; + if(cp == NULL) return(FAIL); + dp = result; + +/* + * Loop until we hit the end of the string + */ + while((c = *cp++)){ + if(c != '%'){ + *dp++ = c; + continue; + }/* End IF */ + + switch(c = *cp++){ + case 'd': + if(argument < 10) goto one; + if(argument < 100) goto two; + /* fall through */ + case '3': + *dp++ = (argument / 100) + '0'; + argument %= 100; + /* fall through */ + case '2': +two: + *dp++ = argument / 10 | '0'; +one: + *dp++ = argument % 10 | '0'; + continue; + case '%': + *dp++ = c; + continue; + + case 'B': + argument = (argument/10 << 4) + argument%10; + continue; + + case 'D': + argument = argument - 2 * (argument%16); + continue; + case 'p': + cp++; /* eat p1, p2 type stuff */ + continue; + + default: + return(FAIL); + }/* End Switch */ + }/* End While */ + + *dp++ = '\0'; + term_puts(result,affected); + return(SUCCESS); + +}/* End Routine */ + +#endif + + + +#ifdef TERMCAP + +/* TERM_PUTS - Output a termcap string with padding + * + * Function: + * + * This routine is called to output a termcap string with padding added. + */ +void +term_puts(termcap_string,lines_affected) +register char *termcap_string; +int lines_affected; +{ +register int delay; +register int mspc10; + +/* + * If this termcap string is undefined, we can't do much for him. + */ + if(termcap_string == NULL) return; + +/* + * Convert the number representing the delay + */ + delay = 0; + while(isdigit((int)*termcap_string)){ + delay = delay * 10 + *termcap_string++ - '0'; + }/* End While */ + +/* + * If there is a decimal place, read up to one decimal and then + * eat all remaining decimal places. + */ + delay *= 10; + if(*termcap_string == '.'){ + termcap_string++; + if(isdigit((int)*termcap_string)) delay += *termcap_string - '0'; + while(isdigit((int)*termcap_string)) termcap_string++; + }/* End IF */ + +/* + * If the delay is followed by a `*', then multiply by the affected + * lines count. + */ + if(*termcap_string == '*'){ + termcap_string++; + delay *= lines_affected; + }/* End IF */ + +/* + * Output all the rest of the string + */ + while(*termcap_string){ + *scr_outbuf_ptr++ = *termcap_string++; + scr_outbuf_left -= 1; + if(scr_outbuf_left <= 0){ + term_flush(); + }/* End IF */ + }/* End While */ + +/* + * If no delay is needed, or we can't figure out what the output + * speed is, then we just give up on the delay. + */ + if(delay == 0) return; + if(term_speed <= 0) return; + if(term_speed >= (sizeof(tmspc10)/sizeof(tmspc10[0]))) return; + +/* + * Round up by half a character frame, and then do the delay. + */ + mspc10 = tmspc10[term_speed]; + delay += mspc10 / 2; + delay /= mspc10; + while(delay > 0){ + delay -= 1; + *scr_outbuf_ptr++ = *termcap_pc; + scr_outbuf_left -= 1; + if(scr_outbuf_left <= 0){ + term_flush(); + }/* End IF */ + }/* End While */ + +}/* End Routine */ + +#endif + + + +/* TERM_GOTO - Send sequence to position the terminal cursor + * + * Function: + * + * This routine provides direct cursoring capability by using the + * CM termcap/terminfo string. This string looks like a printf which + * must be interpreted by this code. This allows pretty much + * arbitrary terminal escape sequences to be supported. The meaning + * of the special characters in the string are: + * + * %d - Same as in printf + * %2 - like %2d + * %3 - like %3d + * %. - gives %c hacking special case characters + * %+x - like %c but adding x first + * + * The following codes affect the state but don't use up a value + * + * %>xy if value > x add y + * %r reverses row column + * %i increments row column (for one origin indexing) + * %% gives % + * %B BCD (2 decimal digits encoded in one byte) + * %D Delta Data (backwards bcd) + * + * all other characters simply get output + */ +int +term_goto(dest_x,dest_y) +int dest_x; +int dest_y; +{ +#ifdef TERMCAP +register char *cp; +register char *dp; +register int c; +register int which; +static char result[40]; +static char added[10]; +char *UP = NULL; +char *BC = NULL; +#endif /* TERMCAP */ + +#ifdef TERMCAP +int oncol = 0; +#endif /* TERMCAP */ + +#ifdef TERMINFO + if(cursor_address == NULL) return(FAIL); + tputs(tparm(cursor_address,dest_y,dest_x),1,term_putc); + curx = dest_x; + cury = dest_y; + return(SUCCESS); +#endif + +#ifdef TERMCAP + cp = termcap_cm; + + if(cp == NULL) return(FAIL); + dp = result; + which = dest_y; + + added[0] = '\0'; + + curx = dest_x; + cury = dest_y; + +/* + * Loop until we hit the end of the string + */ + while((c = *cp++)){ + if(c != '%'){ + *dp++ = c; + continue; + }/* End IF */ + + switch(c = *cp++){ + case 'p': + cp++; /* for p1, p2, or p, eat the digit, because I don't understand what it means yet */ + continue; + case 'n': + dest_x ^= 0140; + dest_y ^= 0140; + goto setwhich; + case 'd': + if(which < 10) goto one; + if(which < 100) goto two; + /* fall through */ + case '3': + *dp++ = (which / 100) + '0'; + which %= 100; + /* fall through */ + case '2': + two: + *dp++ = which / 10 | '0'; + one: + *dp++ = which % 10 | '0'; + swap: + oncol = 1 - oncol; + setwhich: + which = oncol ? dest_x : dest_y; + continue; + + case '>': + if (which > *cp++) which += *cp++; + else cp++; + continue; + + case '+': + which += *cp++; + /* fall into... */ + + case '.': + if( + which == 0 || + which == CNTRL_D || + which == '\t' || + which == '\n' + ){ + if (oncol || UP){ + do { + (void) strcat( + added, + oncol ? (BC ? BC : "\b") : UP + ); + which++; + } while (which == '\n'); + }/* End IF */ + }/* End IF */ + + *dp++ = which; + goto swap; + + case 'r': + oncol = 1; + goto setwhich; + + case 'i': + dest_x++; + dest_y++; + which++; + continue; + + case '%': + *dp++ = c; + continue; + + case 'B': + which = (which/10 << 4) + which%10; + continue; + + case 'D': + which = which - 2 * (which%16); + continue; + + default: +fprintf(stderr,"\n\r\n\rBad char in TGOTO string: %c\n",c); +sleep(5); + return(FAIL); + }/* End Switch */ + }/* End While */ + + (void) strcpy(dp,added); + term_puts(result,1); + return(SUCCESS); + +#else + return(SUCCESS); +#endif + + +}/* End Routine */ + + + +#ifdef TERMCAP + +/* TERM_SCROLL_REGION - Send sequence to set up a scroll region ala VT100 + * + * Function: + * + * This routine is identical to term_goto in most respects, except that + * it is sending the 'set scroll region' escape sequence instead of the + * cursor position sequence. The following escapes are defined for + * substituting row and column: + * + * %d - Same as in printf + * %2 - like %2d + * %3 - like %3d + * %. - gives %c hacking special case characters + * %+x - like %c but adding x first + * + * The following codes affect the state but don't use up a value + * + * %>xy if value > x add y + * %r reverses row column + * %i increments row column (for one origin indexing) + * %% gives % + * %B BCD (2 decimal digits encoded in one byte) + * %D Delta Data (backwards bcd) + * + * all other characters simply get output + */ +int +term_scroll_region(top,bottom) +int top; +int bottom; +{ +register char *cp; +register char *dp; +register int c; +register int which; +int onbottom = 0; +static char result[40]; +static char added[10]; + +char *UP = NULL; +char *BC = NULL; + + cp = termcap_cs; + if(cp == NULL) return(FAIL); + dp = result; + which = top; + + added[0] = '\0'; + + curx = -1; + cury = -1; + +/* + * Loop until we hit the end of the string + */ + while((c = *cp++)){ + if(c != '%'){ + *dp++ = c; + continue; + }/* End IF */ + + switch(c = *cp++){ + case 'n': + top ^= 0140; + bottom ^= 0140; + goto setwhich; + case 'd': + if(which < 10) goto one; + if(which < 100) goto two; + /* fall through */ + case '3': + *dp++ = (which / 100) + '0'; + which %= 100; + /* fall through */ + case '2': +two: + *dp++ = which / 10 | '0'; +one: + *dp++ = which % 10 | '0'; +swap: + onbottom = 1 - onbottom; +setwhich: + which = onbottom ? bottom : top; + continue; + + case '>': + if (which > *cp++) which += *cp++; + else cp++; + continue; + + case '+': + which += *cp++; + /* fall into... */ + + case '.': + if( + which == 0 || + which == CNTRL_D || + which == '\t' || + which == '\n' + ){ + if (onbottom || UP){ + do { + (void) strcat( + added, + onbottom ? (BC ? BC : "\b") : UP + ); + which++; + } while (which == '\n'); + }/* End IF */ + }/* End IF */ + + *dp++ = which; + goto swap; + + case 'r': + onbottom = 1; + goto setwhich; + + case 'i': + top++; + bottom++; + which++; + continue; + + case '%': + *dp++ = c; + continue; + + case 'B': + which = (which/10 << 4) + which%10; + continue; + + case 'D': + which = which - 2 * (which%16); + continue; + + default: + return(FAIL); + }/* End Switch */ + }/* End While */ + + (void) strcpy(dp,added); + term_puts(result,1); + return(SUCCESS); + +}/* End Routine */ + +#endif + +#ifdef TERMINFO + +/* TERM_SCROLL_REGION - Set up a scroll region + * + * Function: + * + * This routine is called to set up a scrolling region on the + * terminal. This is typically being used to emulate insert/delete + * line. + */ +int +term_scroll_region(top,bottom) +int top; +int bottom; +{ + if(change_scroll_region == NULL) return(FAIL); + + tputs(tparm(change_scroll_region,top,bottom),1,term_putc); + return(SUCCESS); + +}/* End Routine */ + +#endif + + + +/* TERM_PUTC - Output a single character + * + * Function: + * + * This routine is called to output a single character to the screen + * output buffer. If this fills the output buffer, flush the buffer. + */ +int +term_putc(data) +//char data; +int data; +{ + *scr_outbuf_ptr++ = data; + scr_outbuf_left -= 1; + + if(scr_outbuf_left <= 0){ + term_flush(); + }/* End IF */ + + return( data ); + +}/* End Routine */ + +/* TERM_FLUSH - Flush any data in the output buffer + * + * Function: + * + * This routine flushes any bytes sitting in the screen output + * buffer to the terminal. + */ +void +term_flush() +{ + register int i; + + i = SCREEN_OUTPUT_BUFFER_SIZE - scr_outbuf_left; + +#ifdef UNIX + if(i) write(tty_output_chan,scr_outbuf,(unsigned)i); +#endif + +#ifdef VMS + if(i){ + i = sys$qiow(0,tty_output_chan,IO$_WRITEVBLK|IO$M_NOFORMAT,0,0,0, + scr_outbuf,i,0,0,0,0); + if(!(i & STS$M_SUCCESS)) exit(i); + }/* End IF*/ +#endif + + scr_outbuf_left = SCREEN_OUTPUT_BUFFER_SIZE; + scr_outbuf_ptr = scr_outbuf; + +}/* End Routine */ + + + +/* INIT_TERMCAP - Read in the termcap description of the tty + * + * Function: + * + * This routine looks up the terminal description in the termcap file, and + * sets up the stuff for the screen package. It also checks that the tty + * has certain basic capabilities that we require. + */ +int +init_term_description() +{ + char termcap_description_buffer[TERMCAP_BUFFER_SIZE]; + char *terminal_name; +#ifdef TERMCAP + char *cp; +#endif + char temp_string[256]; + register int status; + + if(teco_startup == NO) return( SUCCESS ); + + if((terminal_name = getenv("TECO_TERM")) == NULL){ + if((terminal_name = getenv("TERM")) == NULL){ + tec_error(ENOENT,"Environment variable TERM not found"); + }/* End IF */ + }/* End IF */ + + status = tgetent(termcap_description_buffer,terminal_name); + if(status == -1) tec_error(EIO,"Cannot read termcap file"); + if(status == 0){ + sprintf(temp_string,"No termcap entry for terminal type %s", + terminal_name); + tec_error(ESRCH,temp_string); + }/* End IF */ + +/* + * Determine how many rows and columns this terminal has. If either number + * is already non-zero, then it has been set up by some previous code that + * really knows the size of the terminal window, and in this case, we will + * leave it alone. + */ +#ifdef TERMCAP + if(!term_columns) term_columns = tgetnum("co"); + if(!term_lines) term_lines = tgetnum("li"); +#endif + +#ifdef TERMINFO + if(!term_columns) term_columns = tgetnum("columns"); + if(!term_lines) term_lines = tgetnum("lines"); +#endif + +#ifdef TERMCAP +/* + * Now we read the escape sequence description strings for termcap + */ + +/* + * CM is the basic cursor movement string. This allows you to position the + * cursor to any line,column on the terminal. + */ + cp = &temp_string[0]; + termcap_cm = NULL; + if(tgetstr("cm",&cp)){ + termcap_cm = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_cm == NULL) return(FAIL); + (void) strcpy(termcap_cm,temp_string); + }/* End IF */ + else { + tec_error(ENOTTY,"?TECO: Terminal does not support cursor addressing"); + }/* End Else */ + +/* + * TI is a string which needs to be output at init time by programs + * which are going to use CM + */ + cp = &temp_string[0]; + termcap_ti = NULL; + if(tgetstr("ti",&cp)){ + termcap_ti = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_ti == NULL) return(FAIL); + (void) strcpy(termcap_ti,temp_string); + }/* End IF */ + +/* + * TE is a string which needs to be output before exit time by programs + * which have been using CM + */ + cp = &temp_string[0]; + termcap_te = NULL; + if(tgetstr("te",&cp)){ + termcap_te = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_te == NULL) return(FAIL); + (void) strcpy(termcap_te,temp_string); + }/* End IF */ + +/* + * PC is the padd character which is used to fill for terminals which + * require some time after certain sequences. + */ + cp = &temp_string[0]; + termcap_pc = NULL; + if(tgetstr("pc",&cp)){ + termcap_pc = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_pc == NULL) return(FAIL); + (void) strcpy(termcap_pc,temp_string); + }/* End IF */ + else termcap_pc = ""; + +/* + * SG is the number of screen locations it takes to set or clear + * standout mode. It alerts us to brain damaged magic cookie ttys + */ + termcap_sg = tgetnum("sg"); + if(termcap_sg < 0) termcap_sg = 0; + +/* + * SO is the string which will cause the terminal to enter reverse + * video (stand-out) mode. + */ + cp = &temp_string[0]; + termcap_so = NULL; + if(tgetstr("so",&cp)){ + termcap_so = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_so == NULL) return(FAIL); + (void) strcpy(termcap_so,temp_string); + }/* End IF */ + +/* + * SE is the string which will cause the terminal to leave reverse + * video (stand-out) mode and re-enter normal drawing mode. + */ + cp = &temp_string[0]; + termcap_se = NULL; + if(tgetstr("se",&cp)){ + termcap_se = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_se == NULL) return(FAIL); + (void) strcpy(termcap_se,temp_string); + }/* End IF */ + +/* + * CD is the string which will cause the terminal to clear from + * the present cursor location to the end of the screen + */ + cp = &temp_string[0]; + termcap_cd = NULL; + if(tgetstr("cd",&cp)){ + termcap_cd = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_cd == NULL) return(FAIL); + (void) strcpy(termcap_cd,temp_string); + }/* End IF */ + +/* + * CE is the string which will cause the terminal to clear from + * the present cursor location to the end of the current line + */ + cp = &temp_string[0]; + termcap_ce = NULL; + if(tgetstr("ce",&cp)){ + termcap_ce = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_ce == NULL) return(FAIL); + (void) strcpy(termcap_ce,temp_string); + }/* End IF */ + +/* + * cs is the string which will cause the terminal to change the scroll region + * This is mostly useful in emulating insert / delete line on vt100 terminals + */ + cp = &temp_string[0]; + termcap_cs = NULL; + if(tgetstr("cs",&cp)){ + termcap_cs = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_cs == NULL) return(FAIL); + (void) strcpy(termcap_cs,temp_string); + }/* End IF */ + +/* + * sf is the string which will cause the terminal to scroll forward. This is + * used in conjunction with scroll regions for delete line. + */ + cp = &temp_string[0]; + termcap_sf = NULL; + if(tgetstr("sf",&cp)){ + termcap_sf = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_sf == NULL) return(FAIL); + (void) strcpy(termcap_sf,temp_string); + }/* End IF */ + +/* + * sr is the string which will cause the terminal to scroll in reverse. This + * is used in conjunction with scroll regions for insert line. + */ + cp = &temp_string[0]; + termcap_sr = NULL; + if(tgetstr("sr",&cp)){ + termcap_sr = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_sr == NULL) return(FAIL); + (void) strcpy(termcap_sr,temp_string); + }/* End IF */ + +/* + * al is the string which will cause the terminal to insert a blank line at the + * present cursor location. + */ + cp = &temp_string[0]; + termcap_al = NULL; + if(tgetstr("al",&cp)){ + termcap_al = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_al == NULL) return(FAIL); + (void) strcpy(termcap_al,temp_string); + }/* End IF */ + +/* + * dl is the string which will cause the terminal to delete the current line. + * The following lines will all scroll up. + */ + cp = &temp_string[0]; + termcap_dl = NULL; + if(tgetstr("dl",&cp)){ + termcap_dl = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_dl == NULL) return(FAIL); + (void) strcpy(termcap_dl,temp_string); + }/* End IF */ + +/* + * AL is the string which will cause the terminal to insert a blank line at the + * present cursor location. + */ + cp = &temp_string[0]; + termcap_AL_arg = NULL; + if(tgetstr("AL",&cp)){ + termcap_AL_arg = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_AL_arg == NULL) return(FAIL); + (void) strcpy(termcap_AL_arg,temp_string); + }/* End IF */ + +/* + * DL is the string which will cause the terminal to delete the current line. + * The following lines will all scroll up. + */ + cp = &temp_string[0]; + termcap_DL_arg = NULL; + if(tgetstr("DL",&cp)){ + termcap_DL_arg = tec_alloc(TYPE_C_CPERM,strlen(temp_string)+1); + if(termcap_DL_arg == NULL) return(FAIL); + (void) strcpy(termcap_DL_arg,temp_string); + }/* End IF */ + +#endif + + return(SUCCESS); + +}/* End Routine */ + + + +#ifdef VMS +char *current_termcap_description; +char *termcap = +"d1|vt100-80|vt-100|dec vt100:\ +:co#80:li#24:cl=50\\E[;H\\E[2J:bs:am:cm=5\\E[%i%2;%2H:nd=2\\E[C:up=2\\E[A:\ +:ce=3\\E[K:cd=50\\E[J:so=2\\E[7m:se=2\\E[m:us=2\\E[4m:ue=2\\E[m:\ +:is=\\E>\\E[?3l\\E[?4l\\E[?5l\\E[?7h\\E[?8h:ks=\\E[?1h\\E=:ke=\\E[?1l\\E>:\ +:ku=\\E[A:kd=\\E[B:kr=\\E[C:kl=\\E[D:\ +:cs=\\E[%i%d;%dr:sf=5\\ED:\ +:kh=\\E[H:k1=\\EOP:k2=\\EOQ:k3=\\EOR:k4=\\EOS:pt:sr=5\\EM:xn:xv:"; + + +/* TGETENT - Version of TGETENT for Non-UNIX systems + * + * Function: + * + * This routine is used on non-UNIX systems to return a termcap + * description into the specified buffer. + */ +int +tgetent(buffer,term_name) +char *buffer; +char *term_name; +{ +register char *cp = NULL; +register char *dp; +char *temp_buffer = NULL; +char *termcap_filename; +FILE *fd; + +/* + * If current_termcap_description isn't set up yet, check for a logical + * name which points to a termcap file, and see if we can find the entry + * in there. + */ + if(current_termcap_description == NULL){ + termcap_filename = getenv("TERMCAP"); + if(termcap_filename != NULL){ + fd = fopen(termcap_filename,"r"); + if(fd != NULL){ + temp_buffer = malloc(TERMCAP_BUFFER_SIZE); + if(temp_buffer == NULL) return(FAIL); + if(scan_termcap(fd,temp_buffer,term_name)){ + cp = temp_buffer; + }/* End IF */ + fclose(fd); + }/* End IF */ + }/* End IF */ + }/* End IF */ + + if(cp == NULL) cp = termcap; + dp = current_termcap_description = buffer; + + while(*cp != NULL){ + if(cp[0] == '\\'){ + if(cp[1] == 'E' || cp[1] == 'e'){ + *dp++ = ESCAPE; + cp += 2; + continue; + }/* End IF */ + }/* End IF */ + + *dp++ = *cp++; + + }/* End While */ + + if(temp_buffer){ + free(temp_buffer); + temp_buffer = NULL; + }/* End IF */ + + return(1); + +}/* End Routine */ + +/* SCAN_TERMCAP - Attempt to find a terminal description in the termcap file + * + * Function: + * + * This routine scans the termcap file for an entry which matches our + * terminal, and returns non-zero if it finds it. + */ +scan_termcap(fd,temp_buffer,term_name) +FILE *fd; +char *temp_buffer; +char *term_name; +{ +register char *cp; +char *temp_name; +char status; + +/* + * Loop reading terminal descriptions into the buffer + */ + while(1){ + cp = temp_buffer; + bzero(cp,TERMCAP_BUFFER_SIZE); + if(fgets(cp,TERMCAP_BUFFER_SIZE,fd) == NULL) return(0); + if(*cp == '#') continue; +/* + * Ok, we have the start of an entry. Check for continuation. + */ + while(cp[0]){ + if(cp[0] == '\n'){ + cp[1] = '\0'; + break; + }/* End IF */ + if(cp[0] == '\\' && cp[1] == '\n'){ + if(fgets(cp,TERMCAP_BUFFER_SIZE-(cp-temp_buffer),fd) == NULL){ + cp[0] = '\n'; cp[1] = '\0'; + }/* End IF */ + }/* End IF */ + cp++; + }/* End While */ + + cp = temp_name = temp_buffer; + + while(*cp){ + if(*cp == '|'){ + *cp = '\0'; + status = strcmp(temp_name,term_name); + *cp = '|'; + if(status == 0) return(1); + cp++; + temp_name = cp; + continue; + }/* End IF */ + if(*cp == ':'){ + *cp = '\0'; + status = strcmp(temp_name,term_name); + *cp = ':'; + if(status == 0) return(1); + break; + }/* End IF */ + cp++; + }/* End While */ + + }/* End While */ + + return(0); + +}/* End Routine */ + + + +/* TGETNUM - Version of TGETNUM for Non-UNIX systems + * + * Function: + * + * This routine is used on non-UNIX systems to return a specific + * termcap number from the termcap description of our terminal. + */ +int +tgetnum(num_name) +char *num_name; +{ +register char *cp; +int temp; +char minus_seen = 0; + + cp = current_termcap_description; + if(strlen(num_name) != 2){ + fprintf(stderr,"TGETNUM: capability name %s should be 2 bytes long\n", + num_name); + exit(1); + }/* End IF */ + + for(cp = current_termcap_description; *cp != NULL; cp++){ + if(cp[0] != ':') continue; + if(cp[1] != num_name[0]) continue; + if(cp[2] != num_name[1]) continue; + + cp += 3; + if(*cp == '#') cp++; + temp = 0; + if(*cp == '-'){ + minus_seen = 1; + cp++; + }/* End IF */ + + while(isdigit(*cp)){ + temp = temp * 10 + *cp++ - '0'; + }/* End While */ + + if(minus_seen) temp = 0 - temp; + return(temp); + + }/* End FOR */ + + return(0); + +}/* End Routine */ + +/* TGETSTR - Version of TGETSTR for Non-UNIX systems + * + * Function: + * + * This routine is used on non-UNIX systems to return a specific + * termcap string from the termcap description of our terminal. + */ +char * +tgetstr(str_name,buffer_ptr) +char *str_name; +char **buffer_ptr; +{ +register char *cp; +register char *dp; +char *buffer; + + buffer = *buffer_ptr; + cp = current_termcap_description; + dp = buffer; + if(strlen(str_name) != 2){ + fprintf(stderr,"TGETSTR: capability name %s should be 2 bytes long\n", + str_name); + exit(1); + }/* End IF */ + + for(cp = current_termcap_description; *cp != NULL; cp++){ + if(cp[0] != ':') continue; + if(cp[1] != str_name[0]) continue; + if(cp[2] != str_name[1]) continue; + + cp += 3; + if(*cp == '=') cp += 1; + while(*cp != ':' && *cp != NULL){ + *dp++ = *cp++; + }/* End While */ + *dp++ = '\0'; + + return(buffer); + + }/* End FOR */ + + return(NULL); +} + +/* END OF VMS CONDITIONAL CODE */ + +#endif diff --git a/tecundo.c b/tecundo.c new file mode 100644 index 0000000..e0f9422 --- /dev/null +++ b/tecundo.c @@ -0,0 +1,504 @@ +char *tecundo_c_version = "tecundo.c: $Revision: 1.1 $"; + +/* + * $Date: 2007/12/10 21:59:21 $ + * $Source: /cvsroot/videoteco/videoteco/tecundo.c,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecundo.c + * Subroutines to implement the undo capabilities of the parser + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#include "teco.h" +#include "tecparse.h" + + extern struct search_buff search_string; + extern char user_message[]; + extern char exit_flag; + extern char immediate_execute_flag; + extern int last_search_pos1,last_search_pos2; + extern int last_search_status; + + extern struct buff_header *curbuf,*buffer_headers; + extern struct buff_header *qregister_push_down_list; + extern struct tags *current_tags; + void tecundo_list(struct undo_token *); + + + +/* PARSER_UNDO - Execute an UNDO command + * + * Function: + * + * This routine is called with an undo token to reverse some changes + * to the program state. + */ +int +parser_undo(ut) +register struct undo_token *ut; +{ +char tmp_message[LINE_BUFFER_SIZE]; +register struct cmd_token *ct; + + PREAMBLE(); + + switch(ut->opcode){ +/* + * CHANGEDOT is used to undo a movement of dot. iarg1 contains the buffer + * position we want to go back to. + */ + case UNDO_C_CHANGEDOT: + curbuf->dot = ut->iarg1; + break; +/* + * DELETE is used to undo the addition of characters to the buffer. iarg1 + * contains the buffer position to begin deleting at, iarg2 contains the + * number of characters that should be deleted. + */ + case UNDO_C_DELETE: + if(ut->iarg2 > 0){ + buff_delete((struct buff_header *)ut->carg1,ut->iarg1, + ut->iarg2); + }/* End IF */ + break; +/* + * INSERT is used to undo the deletion of characters from the buffer. + * iarg1 contains the position in the buffer of the deletion. iarg2 + * contains a count of the number of bytes which were deleted. carg1 + * is a pointer to a tec_alloc'ed buffer which holds the deleted bytes. + */ + case UNDO_C_INSERT: + buff_insert(curbuf,ut->iarg1,ut->carg1,ut->iarg2); + break; +/* + * UQREGISTER is used to undo the loading of an integer value into a + * Q register. iarg1 is the Q register name, iarg2 is the old value. + */ + case UNDO_C_UQREGISTER: + {/* Local Block */ + register struct buff_header *hbp; + + hbp = buff_qfind(ut->iarg1,0); + if(hbp == NULL){ + sprintf(tmp_message,"?Cannot find Q-register <%c>", + ut->iarg1); + error_message(tmp_message); + return(FAIL); + }/* End IF */ + + hbp->ivalue = ut->iarg2; + break; + }/* End Local Block */ +/* + * MEMFREE allows us to free up memory allocated to a command token or an + * undo token. + */ + case UNDO_C_MEMFREE: + tec_release(TYPE_C_CBUFF,ut->carg1); + ut->carg1 = NULL; + return(SUCCESS); +/* + * SHORTEN_STRING allows us to handle rubouts etc while a string was being + * built. + */ + case UNDO_C_SHORTEN_STRING: + {/* Local Block */ + register int i; + register char *cp; +/* + * IARG1 holds the length we want to shorten the string to. If the string is + * already shorter than that, we leave it alone of course. Note that we use + * IARG2 to hold the address of the string. + */ + i = ut->iarg1; + cp = ut->carg1; + while(i--){ + if(*cp) cp++; + }/* End While */ + *cp = '\0'; + return(SUCCESS); + }/* End Local Block */ +/* + * SET_SEARCH_STRING sets the default search string + */ + case UNDO_C_SET_SEARCH_STRING: + return(SUCCESS); + +/* + * CHANGEBUFF changes the current buffer. We pass a flag to buff_openbuffnum + * so that if we return to buffer zero, it doesn't build a new map. + */ + case UNDO_C_CHANGEBUFF: + + buff_openbuffnum(ut->iarg1,0); + return(SUCCESS); + +/* + * MACRO undoes all the changes a macro made + */ + case UNDO_C_MACRO: + ct = (struct cmd_token *)ut->carg1; + while(ct->next_token) ct = ct->next_token; + while(ct){ +/* + * If this command token has an undo list associated with it, call the + * routine which will undo the entire undo list under this token. + */ + if(ct->undo_list){ + tecundo_list(ct->undo_list); + ct->undo_list = NULL; + }/* End IF */ +/* + * If the command token is not at the head of the list, back up one so + * as to keep it's address while we free up this command token. + */ + if(ct->prev_token){ + ct = ct->prev_token; + free_cmd_token(ct->next_token); + ct->next_token = NULL; + }/* End IF */ +/* + * Else, it IS the head of the list and we can simply free it up and break + * out of the loop. + */ + else { + free_cmd_token(ct); + ut->carg1 = NULL; + break; + }/* End Else */ + }/* End While */ + + return(SUCCESS); + +/* + * SHORTEN_MESSAGE handles rubouts in ^A messages + */ + case UNDO_C_SHORTEN_MESSAGE: + {/* Local Block */ + register int i; + i = strlen(user_message); + if(i > 0) user_message[i-1] = '\0'; + return(SUCCESS); + }/* End Local Block */ + +/* + * BULK_INSERT is used to undo a deleted region + */ + case UNDO_C_BULK_INSERT: + buff_bulk_insert(curbuf, + ut->iarg1,ut->iarg2,(struct buff_line *)ut->carg1); + return(SUCCESS); +/* + * SET_IMMEDIATE_MODE resets the immediate execute state + */ + case UNDO_C_SET_IMMEDIATE_MODE: + immediate_execute_flag = ut->iarg1; + return(SUCCESS); + +/* + * PUSH is used to re-push a Q register onto the Q register push down + * list (stack). + */ + case UNDO_C_PUSH: + push_qregister(ut->iarg1); + return(SUCCESS); +/* + * POP is used to undo a Q register push. It just takes an entry off of + * the Q register pushdown list and destroys it. + */ + case UNDO_C_POP: + {/* Local Block */ + register struct buff_header *hbp; + + hbp = qregister_push_down_list; + if(hbp){ + qregister_push_down_list = hbp->next_header; +/* + * Place it on the main buffer list so that buff_destroy can find it + */ + hbp->next_header = buffer_headers; + buffer_headers = hbp; + buff_destroy(hbp); + }/* End IF */ + + return(SUCCESS); + + }/* End Local Block */ + +/* + * MODIFIED is used to flag a change in the state of the current edit buffers + * modified status. We undo it by clearing the modified flag and clearing the + * indication in the buffer status line display. + */ + case UNDO_C_MODIFIED: + ((struct buff_header *)(ut->carg1))->ismodified = NO; + if(screen_label_line((struct buff_header *)ut->carg1, + "",LABEL_C_MODIFIED) == FAIL){ + return(FAIL); + }/* End IF */ + return(SUCCESS); +/* + * This function undoes the creation of a buffer by the EB command + */ + case UNDO_C_CLOSEBUFF: + buff_destroy((struct buff_header *)ut->carg1); + break; +/* + * This function undoes a window switch + */ + case UNDO_C_WINDOW_SWITCH: + window_switch(ut->iarg1); + break; +/* + * This function undoes a window split + */ + case UNDO_C_WINDOW_SPLIT: + window_switch(ut->iarg1); + screen_delete_window((struct window *)ut->carg1); + break; +/* + * This function reopens a buffer which was closed with EF + */ + case UNDO_C_REOPENBUFF: + buff_reopenbuff((struct buff_header *)ut->carg1); + break; +/* + * This function replaces the buffer name which was changed by a rename + * operation. + */ + case UNDO_C_RENAME_BUFFER: + tec_release(TYPE_C_CBUFF,curbuf->name); + curbuf->name = ut->carg1; + break; + +/* + * This function replaces arguments which may have been overwritten during + * a loop. + */ + case UNDO_C_PRESERVEARGS: + ct = (struct cmd_token *)ut->carg1; + ct->ctx.iarg1 = ut->iarg1; + ct->ctx.iarg2 = ut->iarg2; + break; + +/* + * This function replaces runtime options changed by the EJ command + */ + case UNDO_C_SETOPTIONS: + cmd_setoptions(ut->iarg1,ut->iarg2,NULL); + break; +/* + * This function restores some globals used by the searching system + */ + case UNDO_C_SET_SEARCH_GLOBALS: + last_search_pos1 = ut->iarg1; + last_search_pos2 = ut->iarg2; + last_search_status = (int)ut->carg1; + break; +/* + * This function puts the previous tags file back + */ + case UNDO_C_LOAD_TAGS: + tag_free_struct(current_tags); + current_tags = (struct tags *)ut->carg1; + break; + +/* + * This function puts the previous tag entry selection back + */ + case UNDO_C_SELECT_TAGS: + current_tags->current_entry = (struct tagent *)ut->carg1; + break; + + case UNDO_C_SET_EXIT_FLAG: + exit_flag = 0; + break; + +/* + * Here when we get an undo code we don't know about. This is an internal + * error to teco! + */ + default: + sprintf(tmp_message,"?Unknown undo opcode %d",ut->opcode); + error_message(tmp_message); + return(FAIL); + }/* End Switch */ + + return(SUCCESS); + +}/* End Routine */ + + + +/* ALLOCATE_UNDO_TOKEN - Allocate an undo token structure + * + * Function: + * + * This routine is called to allocate an undo token structure. If there + * is one on the free list, it is used, otherwise we allocate one. UNDO + * tokens are used so that we can back out of operations which change + * some state other than that of the parser. The normal case is that we + * make some change to the edit buffer, and want to be able to back out + * of it if we want. + */ +struct undo_token * +allocate_undo_token(ct) +register struct cmd_token *ct; +{ +register struct undo_token *ut; + + PREAMBLE(); + + ut = (struct undo_token *)tec_alloc(TYPE_C_UNDO,sizeof(struct undo_token)); + if(ut == NULL) return(NULL); + + ut->opcode = UNDO_C_UNUSED; + ut->iarg1 = 0; + ut->iarg2 = 0; + ut->carg1 = NULL; + ut->next_token = NULL; + + if(ct != NULL){ + ut->next_token = ct->undo_list; + ct->undo_list = ut; + }/* End IF */ + + return(ut); + +}/* End Routine */ + + + +/* FREE_UNDO_TOKEN - Routine to place an undo token on the free list + * + * Function: + * + * This routine is called with the address of the undo token + * to be placed on the free list. + */ +void +free_undo_token(ut) +register struct undo_token *ut; +{ + + PREAMBLE(); + + tec_release(TYPE_C_UNDO,(char *)ut); + +}/* End Routine */ + + + +/* TECUNDO_LIST - Cause a list of undo structures to be backed out + * + * Function: + * + * This routine is called with the head of a list of undo tokens that + * need to be undone. They are placed on the list such that the head + * of the list is the first that needs to be undone. + */ +void +tecundo_list(nut) +register struct undo_token *nut; +{ +register struct undo_token *ut; + + PREAMBLE(); + + while((ut = nut)){ + +/* + * Remember what the next token address is and unchain it from the current + * undo token. + */ + nut = ut->next_token; + ut->next_token = NULL; +/* + * Now call the undo routine to back out the changes to the edit buffer that + * this token calls for. Then place the token back on the free list. + */ + parser_undo(ut); + free_undo_token(ut); + + }/* End While */ + +}/* End Routine */ + + + +/* TECUNDO_CLEANUP - Place the list of undo tokens back on the free list + * + * Function: + * + * This routine is called with the head of a list of undo tokens. It + * places them back on the undo free list while releasing any resources + * they may have allocated. For example, the MEMFREE token has to free + * the memory the block points to. + */ +void +tecundo_cleanup(tut) +register struct undo_token *tut; +{ +register struct undo_token *ut; + + PREAMBLE(); + + while((ut = tut)){ + tut = tut->next_token; + switch(ut->opcode){ + case UNDO_C_MEMFREE: + if(ut->carg1) tec_release(TYPE_C_CBUFF,ut->carg1); + ut->carg1 = NULL; + free_undo_token(ut); + break; + case UNDO_C_MACRO: + if(ut->carg1){ + parser_cleanup_ctlist((struct cmd_token *)ut->carg1); + }/* End IF */ + ut->carg1 = NULL; + free_undo_token(ut); + break; + case UNDO_C_BULK_INSERT: + buff_free_line_buffer_list((struct buff_line *)ut->carg1); + ut->carg1 = NULL; + free_undo_token(ut); + break; + case UNDO_C_REOPENBUFF: + {/* Local Block */ + register struct buff_header *hbp; + hbp = (struct buff_header *)ut->carg1; + hbp->next_header = buffer_headers; + buffer_headers = hbp; + buff_destroy(hbp); + }/* End Local Block */ + free_undo_token(ut); + break; + case UNDO_C_RENAME_BUFFER: + tec_release(TYPE_C_CBUFF,ut->carg1); + free_undo_token(ut); + break; + case UNDO_C_LOAD_TAGS: + tag_free_struct((struct tags *)ut->carg1); + break; + default: + free_undo_token(ut); + }/* End Switch */ + }/* End While */ +}/* End Routine */ diff --git a/tecvms.h b/tecvms.h new file mode 100644 index 0000000..fb1cb6c --- /dev/null +++ b/tecvms.h @@ -0,0 +1,51 @@ +/* + * $Date: 2007/12/10 21:59:21 $ + * $Source: /cvsroot/videoteco/videoteco/tecvms.h,v $ + * $Revision: 1.1 $ + * $Locker: $ + */ + +/* tecvms.h + * Include file for VMS build + * %W% (PC) %G% + * + * COPYRIGHT (c) 1985-2003 BY + * PAUL CANTRELL & J. M. NISHINAGA + * SUDBURY, MA 01776 + * ALL RIGHTS RESERVED + * + * This software is furnished in it's current state free of charge. + * The authors reserve all rights to the software. Further + * distribution of the software is not authorized. Modifications to + * the software may be made locally, but shall not be distributed + * without the consent of the authors. This software or any other + * copies thereof, may not be provided or otherwise made available + * to anyone without express permission of the authors. Title to and + * ownership of this software remains with the authors. + * + */ + +#define VMS + +#include file +#include errno +#include stdio +#include ctype +#include perror +#include signal +#include jpidef +#include ssdef +#include iodef +#include stsdef +#include descrip +#include ttdef + +#ifndef LIB$K_CLI_LOCAL_SYM +#define LIB$K_CLI_LOCAL_SYM 1 +#endif +#ifndef CLI$M_NOWAIT +#define CLI$M_NOWAIT (1<<0) +#endif +#ifndef CLI$M_NOTIFY +#define CLI$M_NOTIFY (1<<4) +#endif -- cgit v1.2.3