[comp.sources.unix] v24i005: RCS source control system, Part05/12

rsalz@uunet.uu.net (Rich Salz) (02/22/91)

Submitted-by: Adam Hammer <hammer@cs.purdue.edu>
Posting-number: Volume 24, Issue 5
Archive-name: rcs/part05

#! /bin/sh
# This is a shell archive.  Remove anything before this line, then feed it
# into a shell via "sh file" or similar.  To overwrite existing files,
# type "sh file -c".
# The tool that generated this appeared in the comp.sources.unix newsgroup;
# send mail to comp-sources-unix@uunet.uu.net if you want that tool.
# Contents:  src/rcsfnms.c src/rcslex.c
# Wrapped by rsalz@litchi.bbn.com on Thu Feb 21 14:36:58 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
echo If this archive is complete, you will see the following message:
echo '          "shar: End of archive 5 (of 12)."'
if test -f 'src/rcsfnms.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/rcsfnms.c'\"
else
  echo shar: Extracting \"'src/rcsfnms.c'\" \(26159 characters\)
  sed "s/^X//" >'src/rcsfnms.c' <<'END_OF_FILE'
X/*
X *                     RCS file name handling
X */
X/****************************************************************************
X *                     creation and deletion of /tmp temporaries
X *                     pairing of RCS file names and working file names.
X *                     Testprogram: define PAIRTEST
X ****************************************************************************
X */
X
X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
X   Copyright 1990 by Paul Eggert
X   Distributed under license by the Free Software Foundation, Inc.
X
XThis file is part of RCS.
X
XRCS is free software; you can redistribute it and/or modify
Xit under the terms of the GNU General Public License as published by
Xthe Free Software Foundation; either version 1, or (at your option)
Xany later version.
X
XRCS is distributed in the hope that it will be useful,
Xbut WITHOUT ANY WARRANTY; without even the implied warranty of
XMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
XGNU General Public License for more details.
X
XYou should have received a copy of the GNU General Public License
Xalong with RCS; see the file COPYING.  If not, write to
Xthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
X
XReport problems and direct all questions to:
X
X    rcs-bugs@cs.purdue.edu
X
X*/
X
X
X
X
X/* $Log: rcsfnms.c,v $
X * Revision 5.4  1990/11/01  05:03:43  eggert
X * Permit arbitrary data in comment leaders.
X *
X * Revision 5.3  1990/09/14  22:56:16  hammer
X * added more filename extensions and their comment leaders
X *
X * Revision 5.2  1990/09/04  08:02:23  eggert
X * Fix typo when !RCSSEP.
X *
X * Revision 5.1  1990/08/29  07:13:59  eggert
X * Work around buggy compilers with defective argument promotion.
X *
X * Revision 5.0  1990/08/22  08:12:50  eggert
X * Ignore signals when manipulating the semaphore file.
X * Modernize list of file name extensions.
X * Permit paths of arbitrary length.  Beware file names beginning with "-".
X * Remove compile-time limits; use malloc instead.
X * Permit dates past 1999/12/31.  Make lock and temp files faster and safer.
X * Ansify and Posixate.
X * Don't use access().  Fix test for non-regular files.  Tune.
X *
X * Revision 4.8  89/05/01  15:09:41  narten
X * changed getwd to not stat empty directories.
X * 
X * Revision 4.7  88/08/09  19:12:53  eggert
X * Fix troff macro comment leader bug; add Prolog; allow cc -R; remove lint.
X * 
X * Revision 4.6  87/12/18  11:40:23  narten
X * additional file types added from 4.3 BSD version, and SPARC assembler
X * comment character added. Also, more lint cleanups. (Guy Harris)
X * 
X * Revision 4.5  87/10/18  10:34:16  narten
X * Updating version numbers. Changes relative to 1.1 actually relative
X * to verion 4.3
X * 
X * Revision 1.3  87/03/27  14:22:21  jenkins
X * Port to suns
X * 
X * Revision 1.2  85/06/26  07:34:28  svb
X * Comment leader '% ' for '*.tex' files added.
X * 
X * Revision 4.3  83/12/15  12:26:48  wft
X * Added check for KDELIM in file names to pairfilenames().
X * 
X * Revision 4.2  83/12/02  22:47:45  wft
X * Added csh, red, and sl file name suffixes.
X * 
X * Revision 4.1  83/05/11  16:23:39  wft
X * Added initialization of Dbranch to InitAdmin(). Canged pairfilenames():
X * 1. added copying of path from workfile to RCS file, if RCS file is omitted;
X * 2. added getting the file status of RCS and working files;
X * 3. added ignoring of directories.
X * 
X * Revision 3.7  83/05/11  15:01:58  wft
X * Added comtable[] which pairs file name suffixes with comment leaders;
X * updated InitAdmin() accordingly.
X * 
X * Revision 3.6  83/04/05  14:47:36  wft
X * fixed Suffix in InitAdmin().
X * 
X * Revision 3.5  83/01/17  18:01:04  wft
X * Added getwd() and rename(); these can be removed by defining
X * V4_2BSD, since they are not needed in 4.2 bsd.
X * Changed sys/param.h to sys/types.h.
X *
X * Revision 3.4  82/12/08  21:55:20  wft
X * removed unused variable.
X *
X * Revision 3.3  82/11/28  20:31:37  wft
X * Changed mktempfile() to store the generated file names.
X * Changed getfullRCSname() to store the file and pathname, and to
X * delete leading "../" and "./".
X *
X * Revision 3.2  82/11/12  14:29:40  wft
X * changed pairfilenames() to handle file.sfx,v; also deleted checkpathnosfx(),
X * checksuffix(), checkfullpath(). Semaphore name generation updated.
X * mktempfile() now checks for nil path; freefilename initialized properly.
X * Added Suffix .h to InitAdmin. Added testprogram PAIRTEST.
X * Moved rmsema, trysema, trydiraccess, getfullRCSname from rcsutil.c to here.
X *
X * Revision 3.1  82/10/18  14:51:28  wft
X * InitAdmin() now initializes StrictLocks=STRICT_LOCKING (def. in rcsbase.h).
X * renamed checkpath() to checkfullpath().
X */
X
X
X#include "rcsbase.h"
X
XlibId(fnmsId, "$Id: rcsfnms.c,v 5.4 1990/11/01 05:03:43 eggert Exp $")
X
Xconst char *RCSfilename;
Xchar *workfilename;
Xstruct stat RCSstat, workstat; /* file status for RCS file and working file */
Xint haveworkstat;
X
Xstatic const char rcsdir[] = RCSDIR;
X
X
X#define TEMPNAMES 4 /* must be at least DIRTEMPNAMES (see rcsedit.c) */
Xstatic char tfnames[TEMPNAMES][L_tmpnam];	/* unlink these when done */
Xstatic volatile int tfmade[TEMPNAMES];		/* if these flags are set */
X
X
Xstruct compair {
X	const char *suffix, *comlead;
X};
X
Xstatic const struct compair comtable[] = {
X/* comtable pairs each filename suffix with a comment leader. The comment   */
X/* leader is placed before each line generated by the $Log keyword. This    */
X/* table is used to guess the proper comment leader from the working file's */
X/* suffix during initial ci (see InitAdmin()). Comment leaders are needed   */
X/* for languages without multiline comments; for others they are optional.  */
X	"a",   "-- ",   /* Ada         */
X        "c",   " * ",   /* C           */
X	"C",   "// ",	/* C++ in all its infinite guises */
X	"CC",  "// ",
X	"c++", "// ",
X	"cc",  "// ",
X	"cxx", "// ",
X	"cl",  ";;; ",  /* Common Lisp */
X	"cmf", "C ",	/* CM FORTRAN  */
X	"cs",  " * ",	/* C*          */
X	"el",  "; ",    /* Emacs Lisp  */
X	"f",   "c ",    /* Fortran     */
X	"for", "c ",
X        "h",   " * ",   /* C-header    */
X        "l",   " * ",   /* lex      NOTE: conflict between lex and franzlisp */
X	"lisp",";;; ",	/* Lucid Lisp  */
X        "mac", "; ",    /* macro       vms or dec-20 or pdp-11 macro */
X	"me",  ".\\\" ",/* me-macros   t/nroff*/
X	"ml",  "; ",    /* mocklisp    */
X	"mm",  ".\\\" ",/* mm-macros   t/nroff*/
X	"ms",  ".\\\" ",/* ms-macros   t/nroff*/
X	"p",   " * ",   /* Pascal      */
X	"pl",  "% ",	/* Prolog      */
X	"tex", "% ",	/* TeX	       */
X        "y",   " * ",   /* yacc        */
X	nil,   "# "     /* default for unknown suffix; must always be last */
X};
X
X
X	void
Xffclose(fptr)
XFILE * fptr;
X/* Function: checks ferror(fptr) and aborts the program if there were
X * errors; otherwise closes fptr.
X */
X{       if (ferror(fptr) || fclose(fptr)==EOF)
X		IOerror();
X}
X
X
X
X	char *
Xmaketemp(n)
X	int n;
X/* Create a unique filename using n and the process id and store it
X * into the nth slot in tfnames.
X * Because of storage in tfnames, tempunlink() can unlink the file later.
X * Returns a pointer to the filename created.
X */
X{
X	char *p = tfnames[n];
X
X	if (!tfmade[n]) {
X#if has_tmpnam
X		if (!tmpnam(p))
X#else
X		VOID sprintf(p, "%sRCS%cXXXXXX", tmp(), 'A'+n);
X		if (!mktemp(p))
X#endif
X			faterror("can't make temporary file name");
X	}
X	tfmade[n] = true;
X	return p;
X}
X
X	void
Xtempunlink()
X/* Clean up maketemp() files.  May be invoked by signal handler.
X */
X{
X	register int i;
X
X	for (i = TEMPNAMES;  0 <= --i;  )
X	    if (tfmade[i]) {
X		VOID unlink(tfnames[i]);
X		tfmade[i] = 0;
X	    }
X}
X
X
X	const char *
Xbindex(sp,ch)
X	register const char *sp;
X	int ch;
X/* Function: Finds the last occurrence of character c in string sp
X * and returns a pointer to the character just beyond it. If the
X * character doesn't occur in the string, sp is returned.
X */
X{
X	register const char c=ch, *r;
X        r = sp;
X        while (*sp) {
X                if (*sp++ == c) r=sp;
X        }
X        return r;
X}
X
X
X
X
X
X	static void
XInitAdmin()
X/* function: initializes an admin node */
X{
X	register const char *Suffix;
X        register int i;
X
X	Head=nil; Dbranch=nil; AccessList=nil; Symbols=nil; Locks=nil;
X        StrictLocks=STRICT_LOCKING;
X
X        /* guess the comment leader from the suffix*/
X        Suffix=bindex(workfilename, '.');
X        if (Suffix==workfilename) Suffix= ""; /* empty suffix; will get default*/
X	for (i=0; comtable[i].suffix && strcmp(Suffix,comtable[i].suffix); i++)
X		;
X	Comment.string = comtable[i].comlead;
X	Comment.size = strlen(comtable[i].comlead);
X        Lexinit(); /* Note: if finptr==NULL, reads nothing; only initializes*/
X}
X
X
X#if !RCSSEP
X	static int
XisRCSfilename(f, p)
X	const char *f, *p;
X/* Yield true iff F (with pure file name P) is an RCS file name.  */
X{
X	return
X		p-f <= sizeof(rcsdir)-1  &&
X		((p -= sizeof(rcsdir)-1) == f  ||  p[-1] == SLASH)  &&
X		strncmp(p, rcsdir, sizeof(rcsdir)-1) == 0;
X}
X#endif
X
X#if RCSSEP
X#	define findpair(c,v,f,m) findpairfile(c,v,f)
X#else
X#	define findpair(c,v,f,m) findpairfile(c,v,f,m)
X#endif
X
X	static char *
X#if RCSSEP
Xfindpairfile(argc, argv, fname)
X#else
Xfindpairfile(argc, argv, fname, rcsmatch)
Xint rcsmatch; /* *ARGV must be an RCS file name iff this is set.  */
X#endif
Xint argc; char * argv[], *fname;
X/* Peek ahead in an ARGC-ARGV argument vector for a pathname ending in FNAME.
X * Yield it if found, and set the corresponding pointer in ARGV to nil.
X * Yield FNAME otherwise.
X */
X{
X	register char *arg;
X#if !RCSSEP
X	register char *b;
X#endif
X	if (
X		0 < argc
X#if RCSSEP
X		&& strcmp(bindex(arg = *argv,SLASH), fname) == 0
X#else
X		&& strcmp(b = bindex(arg = *argv,SLASH), fname) == 0
X		&& isRCSfilename(arg, b) == rcsmatch
X#endif
X	) {
X		*argv = nil;
X		return arg;
X        }
X        return fname;
X}
X
X
X	static int
Xhandleworkstat(s)
X	int s;
X{
X	if (s==0  &&  !S_ISREG(workstat.st_mode)) {
X		error("%s isn't a regular file", workfilename);
X		return false;
X	}
X	haveworkstat = errno;
X	return true;
X}
X
Xint getworkstat()
X/* Function: get status of workfilename. */
X{
X	errno = 0;
X	return handleworkstat(stat(workfilename, &workstat));
X}
X
X	int
Xgetfworkstat(f)
X	int f;
X/* Get status of file descriptor f. */
X{
X	errno = 0;
X	return handleworkstat(fstat(f, &workstat));
X}
X
X
X#if defined(_POSIX_NO_TRUNC) & _POSIX_NO_TRUNC!=-1
X#	define LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED 0
X#else
X#	define LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED 1
X#endif
X
X#if LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED
X#ifdef NAME_MAX
X#	define filenametoolong(path) (NAME_MAX < strlen(bindex(path,SLASH)))
X#else
X	static int
Xfilenametoolong(path)
X	char *path;
X/* Yield true if the last file name in PATH is too long. */
X{
X	static unsigned long dot_namemax;
X
X	register size_t namelen;
X	register char *lastslash;
X	register unsigned long namemax;
X
X	lastslash = strrchr(path, SLASH);
X	namelen = strlen(lastslash ? lastslash+1 : path);
X	if (namelen <= _POSIX_NAME_MAX) /* fast check for shorties */
X		return false;
X	if (lastslash) {
X		*lastslash = 0;
X		namemax = pathconf(path, _PC_NAME_MAX);
X		*lastslash = SLASH;
X	} else {
X		/* Cache the results for the working directory, for speed. */
X		if (!dot_namemax)
X			dot_namemax = pathconf(".", _PC_NAME_MAX);
X		namemax = dot_namemax;
X	}
X	/* If pathconf() yielded -1, namemax is now ULONG_MAX.  */
X	return namemax<namelen;
X}
X#endif
X#endif
X
X	void
Xbufalloc(b, size)
X	register struct buf *b;
X	size_t size;
X/* Ensure *B is a name buffer of at least SIZE bytes.
X * *B's old contents can be freed; *B's new contents are undefined.
X */
X{
X	if (b->size < size) {
X		if (b->size)
X			tfree(b->string);
X		else
X			b->size = sizeof(malloc_type);
X		while (b->size < size)
X			b->size <<= 1;
X		b->string = tnalloc(char, b->size);
X	}
X}
X
X	void
Xbufrealloc(b, size)
X	register struct buf *b;
X	size_t size;
X/* like bufalloc, except *B's old contents, if any, are preserved */
X{
X	if (b->size < size) {
X		if (!b->size)
X			bufalloc(b, size);
X		else {
X			while ((b->size <<= 1)  <  size)
X				;
X			b->string = (char *)testrealloc((malloc_type)b->string, b->size);
X		}
X	}
X}
X
X	void
Xbufautoend(b)
X	struct buf *b;
X/* Free an auto buffer at block exit. */
X{
X	if (b->size)
X		tfree(b->string);
X}
X
X	char *
Xbufenlarge(b, alim)
X	register struct buf *b;
X	const char **alim;
X/* Make *B larger.  Set *ALIM to its new limit, and yield the relocated value
X * of its old limit.
X */
X{
X	size_t s = b->size;
X	bufrealloc(b, s + 1);
X	*alim = b->string + b->size;
X	return b->string + s;
X}
X
X	void
Xbufscat(b, s)
X	struct buf *b;
X	const char *s;
X/* Concatenate S to B's end. */
X{
X	size_t blen  =  b->string ? strlen(b->string) : 0;
X	bufrealloc(b, blen+strlen(s)+1);
X	VOID strcpy(b->string+blen, s);
X}
X
X	void
Xbufscpy(b, s)
X	struct buf *b;
X	const char *s;
X/* Copy S into B. */
X{
X	bufalloc(b, strlen(s)+1);
X	VOID strcpy(b->string, s);
X}
X
X
X	FILE *
Xrcsreadopen(RCSname)
X	const char *RCSname;
X/* Open RCSNAME for reading and yield its FILE* descriptor.
X * Pass this routine to pairfilenames() for read-only access to the file.  */
X{
X	FILE *f;
X	seteid();
X	f = fopen(RCSname, "r");
X	setrid();
X	return f;
X}
X
X	int
Xpairfilenames(argc, argv, rcsopen, mustread, tostdout)
X	int argc;
X	char **argv;
X	FILE *(*rcsopen)P((const char*));
X	int mustread, tostdout;
X/* Function: Pairs the filenames pointed to by argv; argc indicates
X * how many there are.
X * Places a pointer to the RCS filename into RCSfilename,
X * and a pointer to the name of the working file into workfilename.
X * If both the workfilename and the RCS filename are given, and tostdout
X * is true, a warning is printed.
X *
X * If the RCS file exists, places its status into RCSstat.
X *
X * If the RCS file exists, it is RCSOPENed for reading, the file pointer
X * is placed into finptr, and the admin-node is read in; returns 1.
X * If the RCS file does not exist and mustread is set, an error is printed
X * and 0 returned.
X * If the RCS file does not exist and !mustread, the admin node
X * is initialized and -1 returned.
X *
X * 0 is returned on all errors, e.g. files that are not regular files.
X */
X{
X	static struct buf RCSbuf, tempbuf;
X
X	register char *p, *arg, *tempfilename, *RCS1;
X	const char *purefname, *pureRCSname;
X	FILE *lock1;
X
X	if (!(arg = *argv)) return 0; /* already paired filename */
X	if (*arg == '-') {
X		error("%s option is ignored after file names", arg);
X		return 0;
X	}
X
X	/* Allocate buffer temporary to hold the default paired file name. */
X	for (purefname = p = arg; *p; )
X		switch (*p++) {
X		    case SLASH:
X			purefname = p;
X			break;
X		    /* Beware characters that cause havoc with ci -k. */
X		    case KDELIM:
X			error("RCS file name `%s' contains %c", arg, KDELIM);
X			return 0;
X		    case ' ': case '\n': case '\t':
X			error("RCS file name `%s' contains white space", arg);
X			return 0;
X		}
X	bufalloc(&tempbuf, p - purefname + 3);
X	tempfilename = tempbuf.string;
X
X        /* first check suffix to see whether it is an RCS file or not */
X#if RCSSEP
X	if (purefname<(p-=2) && p[0]==RCSSEP && p[1]==RCSSUF)
X#else
X	if (isRCSfilename(arg, purefname))
X#endif
X	{
X                /* RCS file name given*/
X		RCS1 = arg;
X		pureRCSname = purefname;
X                /* derive workfilename*/
X		VOID strcpy(tempfilename, purefname);
X		tempfilename[p - purefname] = 0;
X                /* try to find workfile name among arguments */
X		workfilename = findpair(argc-1,argv+1,tempfilename,false);
X        } else {
X                /* working file given; now try to find RCS file */
X		workfilename = arg;
X                /* derive RCS file name*/
X		VOID sprintf(tempfilename,"%s%c%c", purefname, RCSSEP, RCSSUF);
X                /* Try to find RCS file name among arguments*/
X		RCS1 = findpair(argc-1,argv+1,tempfilename,true);
X                pureRCSname=bindex(RCS1, SLASH);
X        }
X        /* now we have a (tentative) RCS filename in RCS1 and workfilename  */
X        /* Second, try to find the right RCS file */
X        if (pureRCSname!=RCS1) {
X                /* a path for RCSfile is given; single RCS file to look for */
X		errno = 0;
X		RCSfilename = p = RCS1;
X		finptr = (*rcsopen)(RCSfilename = p = RCS1);
X        } else {
X		/* no path for RCS file name. Prefix it with path of work */
X		/* file if RCS file omitted.  Try RCSDIR subdirectory 1st.*/
X		bufalloc(&RCSbuf, strlen(workfilename)+sizeof(rcsdir)+2);
X		RCSfilename = p = RCSbuf.string;
X		if (RCS1==tempfilename) {
X			/* RCS file name not given; prepend work path */
X			VOID strncpy(p, arg, purefname-arg);
X			p += purefname-arg;
X		}
X		VOID strcpy(p, rcsdir);
X		VOID strcpy(p+sizeof(rcsdir)-1, RCS1);
X
X		/* Try D/RCS/file,v. */
X		errno = 0;
X		if (!(finptr = (*rcsopen)(RCSfilename))
X		    &&  (errno==ENOTDIR || errno==ENOENT)
X		    /*
X		     * Many (broken) systems yield ENOENT, not ENOTDIR,
X		     * when the problem is a missing RCS subdirectory.
X		     */
X		) {
X			lock1 = frewrite;
X
X			/* Try D/file,v. */
X			VOID strcpy(p, RCS1);
X			errno = 0;
X			if (!(finptr=(*rcsopen)(RCSfilename)) && errno==ENOENT) {
X			    /*
X			     * Neither file exists; determine the default.
X			     * Prefer D/RCS/file,v to D/file,v.
X			     */
X			    if (mustread || lock1) {
X				/* Switch back to D/RCS/file,v. */
X				VOID strcpy(p, rcsdir);
X				VOID strcpy(p+sizeof(rcsdir)-1, RCS1);
X			    }
X			}
X		}
X		p = RCSbuf.string;
X        }
X	if (finptr) {
X		if (fstat(fileno(finptr), &RCSstat) < 0)
X			efaterror(p);
X		if (!S_ISREG(RCSstat.st_mode)) {
X			error("%s isn't a regular file -- ignored", p);
X                        return 0;
X                }
X                Lexinit(); getadmin();
X	} else {
X		if (errno!=ENOENT || mustread || !frewrite) {
X			if (errno == EEXIST)
X				error("RCS file %s is in use", p);
X			else
X				eerror(p);
X			return 0;
X		}
X                InitAdmin();
X        };
X#	if LONG_NAMES_MAY_BE_SILENTLY_TRUNCATED
X	    if (filenametoolong(p)) {
X		error("RCS file name %s is too long", p);
X		return 0;
X	    }
X#	    ifndef NAME_MAX
X		/*
X		 * Check workfilename, even though it is shorter,
X		 * because it may reside on a different filesystem.
X		 */
X		if (filenametoolong(workfilename)) {
X		    error("working file name %s is too long", workfilename);
X		    return 0;
X		}
X#	    endif
X#	endif
X
X        if (tostdout&&
X            !(RCS1==tempfilename||workfilename==tempfilename))
X                /*The last term determines whether a pair of        */
X                /* file names was given in the argument list        */
X                warn("Option -p is set; ignoring output file %s",workfilename);
X
X	return finptr ? 1 : -1;
X}
X
X
X	const char *
XgetfullRCSname()
X/* Function: returns a pointer to the full path name of the RCS file.
X * Gets the working directory's name at most once.
X * Removes leading "../" and "./".
X */
X{
X	static const char *wd;
X	static struct buf rcsbuf, wdbuf;
X	static size_t pathlength;
X
X	register const char *realname;
X	register size_t parentdirlength;
X	register unsigned dotdotcounter;
X	register char *d;
X
X	if (ROOTPATH(RCSfilename)) {
X                return(RCSfilename);
X        } else {
X		if (!wd) { /* Get working directory for the first time. */
X		    if (!(d = cgetenv("PWD"))) {
X			bufalloc(&wdbuf, 1 +
X#			    ifdef PATH_MAX
X				PATH_MAX
X#			    else
X				_POSIX_PATH_MAX
X#			    endif
X			);
X			errno = 0;
X#			if !has_getcwd
X			    d = getwd(wdbuf.string);
X#			else
X			    while (
X				    !(d = getcwd(wdbuf.string,(int)wdbuf.size))
X				&&  errno==ERANGE
X			    )
X				bufalloc(&wdbuf, wdbuf.size<<1);
X#			endif
X			if (!d)
X			    efaterror("working directory");
X		    }
X		    pathlength = strlen(d);
X		    while (pathlength && d[pathlength-1]==SLASH) {
X			d[--pathlength] = 0;
X                        /* Check needed because some getwd implementations */
X                        /* generate "/" for the root.                      */
X                    }
X		    wd = d;
X                }
X                /*the following must be redone since RCSfilename may change*/
X		/* Find how many `../'s to remove from RCSfilename.  */
X                dotdotcounter =0;
X                realname = RCSfilename;
X                while( realname[0]=='.' &&
X                      (realname[1]==SLASH||(realname[1]=='.'&&realname[2]==SLASH))){
X                        if (realname[1]==SLASH) {
X                            /* drop leading ./ */
X                            realname += 2;
X                        } else {
X                            /* drop leading ../ and remember */
X                            dotdotcounter++;
X                            realname += 3;
X                        }
X                }
X		/* Now remove dotdotcounter trailing directories from wd. */
X		parentdirlength = pathlength;
X		while (dotdotcounter && parentdirlength) {
X                    /* move pointer backwards over trailing directory */
X		    if (wd[--parentdirlength] == SLASH) {
X                        dotdotcounter--;
X                    }
X                }
X		if (dotdotcounter) {
X                    error("can't generate full path name for RCS file");
X                    return RCSfilename;
X                } else {
X                    /* build full path name */
X		    bufalloc(&rcsbuf, parentdirlength+strlen(realname)+2);
X		    VOID strncpy(rcsbuf.string, wd, parentdirlength);
X		    rcsbuf.string[parentdirlength++] = SLASH;
X		    VOID strcpy(rcsbuf.string+parentdirlength, realname);
X		    return rcsbuf.string;
X		}
X        }
X}
X
X	const char *
Xtmp()
X/* Yield the name of the tmp directory, with a trailing SLASH.  */
X{
X	static const char *s;
X	if (!s)
X		if (!(s = getenv("TMP")))
X			s = TMPDIR;
X		else {
X			size_t l = strlen(s);
X			int extra = l && s[l-1]!=SLASH;
X			char *p = ftnalloc(char, l + extra + 1);
X			VOID strcpy(p, s);
X			if (extra) {
X				p[l] = SLASH;
X				p[l+1] = 0;
X			}
X			s = p;
X		}
X	return s;
X}
X
X
X#if !has_rename | bad_rename
X
X	int
Xre_name(from, to)
X	const char *from, *to;
X/* Function: renames a file with the name given by from to the name given by to.
X * unlinks the to-file if it already exists. returns -1 on error, 0 otherwise.
X */
X{       VOID unlink(to);      /* no need to check return code; will be caught by link*/
X                         /* no harm done if file "to" does not exist            */
X#if has_rename
X	return rename(from,to);
X#else
X        if (link(from,to)<0) return -1;
X        return(unlink(from));
X#endif
X}
X
X#endif
X
X
X#if !has_getcwd & !has_getwd
X
X#if !MAKEDEPEND
X#include <sys/dir.h>
X#endif
X
X
X#define dot     "."
X#define dotdot  ".."
X
X
X
Xchar * getwd(name)
Xchar * name;
X/* Function: places full pathname of current working directory into name and
X * returns name on success, NULL on failure.
X * getwd is an adaptation of pwd. May not return to the current directory on
X * failure.
X */
X{
X        FILE    *file;
X        struct  stat    d, dd;
X        char buf[2];    /* to NUL-terminate dir.d_name */
X        struct  direct  dir;
X
X        int rdev, rino;
X        int off;
X        register i,j;
X
X	off = 0;
X	name[0] = SLASH;
X        name[1] = '\0';
X        buf[0] = '\0';
X	if (stat(name, &d)<0) return NULL;
X        rdev = d.st_dev;
X        rino = d.st_ino;
X        for (;;) {
X                if (stat(dot, &d)<0) return NULL;
X                if (d.st_ino==rino && d.st_dev==rdev) {
X			if (name[off] == SLASH)
X				name[off] = '\0';
X			VOID chdir(name); /*change back to current directory*/
X                        return name;
X                }
X                if ((file = fopen(dotdot,"r")) == NULL) return NULL;
X                if (fstat(fileno(file), &dd)<0) goto fail;
X		VOID chdir(dotdot);
X                if(d.st_dev == dd.st_dev) {
X                        if(d.st_ino == dd.st_ino) {
X			    if (name[off] == SLASH)
X				name[off] = 0;
X			    VOID chdir(name); /*change back to current directory*/
X			    ffclose(file);
X                            return name;
X                        }
X                        do {
X                            if (fread((char *)&dir, sizeof(dir), 1, file) !=1)
X                                goto fail;
X                        } while (dir.d_ino != d.st_ino);
X                }
X                else do {
X                        if(fread((char *)&dir, sizeof(dir), 1, file) != 1) {
X                            goto fail;
X                        }
X                        if (dir.d_ino == 0)
X			    dd.st_ino = d.st_ino + 1;
X                        else if (stat(dir.d_name, &dd) < 0)
X			    goto fail;
X                } while(dd.st_ino != d.st_ino || dd.st_dev != d.st_dev);
X		ffclose(file);
X
X                /* concatenate file name */
X                i = -1;
X                while (dir.d_name[++i] != 0);
X                for(j=off+1; j>0; --j)
X                        name[j+i+1] = name[j];
X                off=i+off+1;
X		name[i+1] = SLASH;
X                for(--i; i>=0; --i)
X                        name[i+1] = dir.d_name[i];
X        } /* end for */
X
Xfail:   ffclose(file);
X        return NULL;
X}
X
X
X#endif
X
X
X#ifdef PAIRTEST
X/* test program for pairfilenames() and getfullRCSname() */
X
Xconst char cmdid[] = "pair";
X
Xmain(argc, argv)
Xint argc; char *argv[];
X{
X        int result;
X        int initflag,tostdout;
X        quietflag=tostdout=initflag=false;
X
X        while(--argc, ++argv, argc>=1 && ((*argv)[0] == '-')) {
X                switch ((*argv)[1]) {
X
X                case 'p':       tostdout=true;
X                                break;
X                case 'i':       initflag=true;
X                                break;
X                case 'q':       quietflag=true;
X                                break;
X                default:        error("unknown option: %s", *argv);
X                                break;
X                }
X        }
X
X        do {
X                RCSfilename=workfilename=nil;
X		result = pairfilenames(argc,argv,rcsreadopen,!initflag,tostdout);
X                if (result!=0) {
X		    diagnose("RCS file: %s; working file: %s\nFull RCS file name: %s\n",
X			     RCSfilename,workfilename,getfullRCSname()
X		    );
X                }
X                switch (result) {
X                        case 0: continue; /* already paired file */
X
X                        case 1: if (initflag) {
X                                    error("RCS file %s exists already",RCSfilename);
X                                } else {
X				    diagnose("RCS file %s exists\n",RCSfilename);
X                                }
X				ffclose(finptr);
X                                break;
X
X			case -1:diagnose("RCS file doesn't exist\n");
X                                break;
X                }
X
X        } while (++argv, --argc>=1);
X
X}
X#endif
END_OF_FILE
  if test 26159 -ne `wc -c <'src/rcsfnms.c'`; then
    echo shar: \"'src/rcsfnms.c'\" unpacked with wrong size!
  fi
  # end of 'src/rcsfnms.c'
fi
if test -f 'src/rcslex.c' -a "${1}" != "-c" ; then 
  echo shar: Will not clobber existing file \"'src/rcslex.c'\"
else
  echo shar: Extracting \"'src/rcslex.c'\" \(24012 characters\)
  sed "s/^X//" >'src/rcslex.c' <<'END_OF_FILE'
X/*
X *                     RCS file input
X */
X/*********************************************************************************
X *                     Lexical Analysis.
X *                     hashtable, Lexinit, nextlex, getlex, getkey,
X *                     getid, getnum, readstring, printstring, savestring,
X *                     checkid, fatserror, error, faterror, warn, diagnose
X *                     Testprogram: define LEXDB
X *********************************************************************************
X */
X
X/* Copyright (C) 1982, 1988, 1989 Walter Tichy
X   Copyright 1990 by Paul Eggert
X   Distributed under license by the Free Software Foundation, Inc.
X
XThis file is part of RCS.
X
XRCS is free software; you can redistribute it and/or modify
Xit under the terms of the GNU General Public License as published by
Xthe Free Software Foundation; either version 1, or (at your option)
Xany later version.
X
XRCS is distributed in the hope that it will be useful,
Xbut WITHOUT ANY WARRANTY; without even the implied warranty of
XMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
XGNU General Public License for more details.
X
XYou should have received a copy of the GNU General Public License
Xalong with RCS; see the file COPYING.  If not, write to
Xthe Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
X
XReport problems and direct all questions to:
X
X    rcs-bugs@cs.purdue.edu
X
X*/
X
X
X
X/* $Log: rcslex.c,v $
X * Revision 5.5  1990/12/04  05:18:47  eggert
X * Use -I for prompts and -q for diagnostics.
X *
X * Revision 5.4  1990/11/19  20:05:28  hammer
X * no longer gives warning about unknown keywords if -q is specified
X *
X * Revision 5.3  1990/11/01  05:03:48  eggert
X * When ignoring unknown phrases, copy them to the output RCS file.
X *
X * Revision 5.2  1990/09/04  08:02:27  eggert
X * Count RCS lines better.
X *
X * Revision 5.1  1990/08/29  07:14:03  eggert
X * Work around buggy compilers with defective argument promotion.
X *
X * Revision 5.0  1990/08/22  08:12:55  eggert
X * Remove compile-time limits; use malloc instead.
X * Report errno-related errors with perror().
X * Ansify and Posixate.  Add support for ISO 8859.
X * Use better hash function.
X *
X * Revision 4.6  89/05/01  15:13:07  narten
X * changed copyright header to reflect current distribution rules
X * 
X * Revision 4.5  88/08/28  15:01:12  eggert
X * Don't loop when writing error messages to a full filesystem.
X * Flush stderr/stdout when mixing output.
X * Yield exit status compatible with diff(1).
X * Shrink stdio code size; allow cc -R; remove lint.
X * 
X * Revision 4.4  87/12/18  11:44:47  narten
X * fixed to use "varargs" in "fprintf"; this is required if it is to
X * work on a SPARC machine such as a Sun-4
X * 
X * Revision 4.3  87/10/18  10:37:18  narten
X * Updating version numbers. Changes relative to 1.1 actually relative
X * to version 4.1
X * 
X * Revision 1.3  87/09/24  14:00:17  narten
X * Sources now pass through lint (if you ignore printf/sprintf/fprintf 
X * warnings)
X * 
X * Revision 1.2  87/03/27  14:22:33  jenkins
X * Port to suns
X * 
X * Revision 4.1  83/03/25  18:12:51  wft
X * Only changed $Header to $Id.
X * 
X * Revision 3.3  82/12/10  16:22:37  wft
X * Improved error messages, changed exit status on error to 1.
X *
X * Revision 3.2  82/11/28  21:27:10  wft
X * Renamed ctab to map and included EOFILE; ctab is now a macro in rcsbase.h.
X * Added fflsbuf(), fputs(), and fprintf(), which abort the RCS operations
X * properly in case there is an IO-error (e.g., file system full).
X *
X * Revision 3.1  82/10/11  19:43:56  wft
X * removed unused label out:;
X * made sure all calls to getc() return into an integer, not a char.
X */
X
X
X/*
X#define LEXDB
X*/
X/* version LEXDB is for testing the lexical analyzer. The testprogram
X * reads a stream of lexemes, enters the revision numbers into the
X * hashtable, and prints the recognized tokens. Keywords are recognized
X * as identifiers.
X */
X
X
X
X#include "rcsbase.h"
X
XlibId(lexId, "$Id: rcslex.c,v 5.5 1990/12/04 05:18:47 eggert Exp $")
X
Xstatic struct hshentry *nexthsh;  /*pointer to next hash entry, set by lookup*/
X
Xenum tokens     nexttok;    /*next token, set by nextlex                    */
X
Xint             hshenter;   /*if true, next suitable lexeme will be entered */
X                            /*into the symbol table. Handle with care.      */
Xint             nextc;      /*next input character, initialized by Lexinit  */
X
Xunsigned long	rcsline;    /*current line-number of input		    */
Xint             nerror;     /*counter for errors                            */
Xint             quietflag;  /*indicates quiet mode                          */
XFILE *          finptr;     /*input file descriptor                         */
X
XFILE *          frewrite;   /*file descriptor for echoing input             */
X
XFILE *		foutptr;	    /* copy of frewrite, but NULL to suppress echo  */
X
Xstatic struct buf tokbuf;   /* token buffer				    */
X
Xconst char *    NextString; /* next token				    */
X
X/*
X * Our hash algorithm is h[0] = 0, h[i+1] = 4*h[i] + c,
X * so hshsize should be odd.
X * See B J McKenzie, R Harries & T Bell, Selecting a hashing algorithm,
X * Software--practice & experience 20, 2 (Feb 1990), 209-224.
X */
X#ifndef hshsize
X#	define hshsize 511
X#endif
X
Xstatic struct hshentry *hshtab[hshsize]; /*hashtable			    */
X
Xstatic int ignored_phrases; /* have we ignored phrases in this RCS file? */
X
X    void
Xwarnignore()
X{
X    if (! (ignored_phrases|quietflag)) {
X	ignored_phrases = true;
X	warn("Unknown phrases like `%s ...;' are in the RCS file.", NextString);
X    }
X}
X
X
X
X	static void
Xlookup(str)
X	const char *str;
X/* Function: Looks up the character string pointed to by str in the
X * hashtable. If the string is not present, a new entry for it is created.
X * In any case, the address of the corresponding hashtable entry is placed
X * into nexthsh.
X */
X{
X	register unsigned ihash;  /* index into hashtable */
X	register const char *sp;
X	register struct hshentry *n, **p;
X
X        /* calculate hash code */
X	sp = str;
X        ihash = 0;
X	while (*sp)
X		ihash  =  (ihash<<2) + *sp++;
X	ihash %= hshsize;
X
X	for (p = &hshtab[ihash];  ;  p = &n->nexthsh)
X		if (!(n = *p)) {
X			/* empty slot found */
X			*p = n = ftalloc(struct hshentry);
X			n->num = fstrsave(str);
X			n->nexthsh = nil;
X#			ifdef LEXDB
X				VOID printf("\nEntered: %s at %u ", str, ihash);
X#			endif
X			break;
X		} else if (strcmp(str, n->num) == 0)
X			/* match found */
X			break;
X	nexthsh = n;
X	NextString = n->num;
X}
X
X
X
X
X
X
X	void
XLexinit()
X/* Function: Initialization of lexical analyzer:
X * initializes the hashtable,
X * initializes nextc, nexttok if finptr != NULL
X */
X{       register int            c;
X
X	for (c = hshsize;  0 <= --c;  ) {
X		hshtab[c] = nil;
X        }
X
X	hshenter=true; rcsline=1; nerror=0;
X	ignored_phrases = false;
X	bufrealloc(&tokbuf, 2);
X        if (finptr) {
X		GETC(finptr,foutptr,c);
X		nextc = c; /*initial character*/
X		nexttok = DELIM;  /* anything but EOFILE */
X                nextlex();            /*initial token*/
X        } else {
X                nextc = '\0';
X                nexttok=EOFILE;
X        }
X}
X
X
X	static exiting void
XunexpectedEOF()
X{
X	fatserror("unexpected EOF");
X}
X
X
X
X
X
X
X
X	void
Xnextlex()
X
X/* Function: Reads the next token and sets nexttok to the next token code.
X * Only if hshenter is set, a revision number is entered into the
X * hashtable and a pointer to it is placed into nexthsh.
X * This is useful for avoiding that dates are placed into the hashtable.
X * For ID's and NUM's, NextString is set to the character string.
X * Assumption: nextc contains the next character.
X */
X{       register c;
X	register FILE * fin, * frew;
X        register char * sp;
X	const char *lim;
X        register enum tokens d;
X
X	if (nexttok == EOFILE)
X		unexpectedEOF();
X	fin=finptr; frew=foutptr;
X
X	for (;;) switch ((nexttok=ctab[nextc])) {
X
X	default:
X		fatserror("unknown character `%c'", nextc);
X		/*NOTREACHED*/
X
X        case NEWLN:
X		++rcsline;
X#               ifdef LEXDB
X		afputc('\n',stdout);
X#               endif
X                /* Note: falls into next case */
X
X        case SPACE:
X		GETC(fin,frew,c);
X		nextc = c;
X		continue;
X
X        case EOFILE:
X                return;
X
X        case DIGIT:
X		sp = tokbuf.string;
X		lim = sp + tokbuf.size;
X                *sp++ = nextc;
X		for (;;) {
X			GETC(fin,frew,c);
X			if ((d=ctab[c])!=DIGIT && d!=PERIOD)
X				break;
X                        *sp++ = c;         /* 1.2. and 1.2 are different */
X			if (lim <= sp)
X				sp = bufenlarge(&tokbuf, &lim);
X                }
X		*sp = 0;
X                nextc = c;
X		if (hshenter)
X			lookup(tokbuf.string);
X		else
X			NextString = fstrsave(tokbuf.string);
X                nexttok = NUM;
X                return;
X
X
X        case LETTER:
X	case Letter:
X		sp = tokbuf.string;
X		lim = sp + tokbuf.size;
X                *sp++ = nextc;
X		for (;;) {
X			GETC(fin,frew,c);
X			if ((d=ctab[c])!=LETTER && d!=Letter && d!=DIGIT && d!=IDCHAR)
X				break;
X                        *sp++ = c;
X			if (lim <= sp)
X				sp = bufenlarge(&tokbuf, &lim);
X                }
X		*sp = 0;
X                nextc = c;
X		NextString = fstrsave(tokbuf.string);
X                nexttok = ID;  /* may be ID or keyword */
X                return;
X
X        case SBEGIN: /* long string */
X                nexttok = STRING;
X                /* note: only the initial SBEGIN has been read*/
X                /* read the string, and reset nextc afterwards*/
X                return;
X
X	case COLON:
X	case SEMI:
X		GETC(fin,frew,c);
X		nextc = c;
X                return;
X        }
X}
X
X
Xint getlex(token)
Xenum tokens token;
X/* Function: Checks if nexttok is the same as token. If so,
X * advances the input by calling nextlex and returns true.
X * otherwise returns false.
X * Doesn't work for strings and keywords; loses the character string for ids.
X */
X{
X        if (nexttok==token) {
X                nextlex();
X                return(true);
X        } else  return(false);
X}
X
X	int
Xgetkeyopt(key)
X	const char *key;
X/* Function: If the current token is a keyword identical to key,
X * advances the input by calling nextlex and returns true;
X * otherwise returns false.
X */
X{
X	if (nexttok==ID  &&  strcmp(key,NextString) == 0) {
X		 /* match found */
X		 ffree1(NextString);
X		 nextlex();
X		 return(true);
X        }
X        return(false);
X}
X
X	void
Xgetkey(key)
X	const char *key;
X/* Check that the current input token is a keyword identical to key,
X * and advance the input by calling nextlex.
X */
X{
X	if (!getkeyopt(key))
X		fatserror("missing '%s' keyword", key);
X}
X
X	void
Xgetkeystring(key)
X	const char *key;
X/* Check that the current input token is a keyword identical to key,
X * and advance the input by calling nextlex; then look ahead for a string.
X */
X{
X	getkey(key);
X	if (nexttok != STRING)
X		fatserror("missing string after '%s' keyword", key);
X}
X
X
X	const char *
Xgetid()
X/* Function: Checks if nexttok is an identifier. If so,
X * advances the input by calling nextlex and returns a pointer
X * to the identifier; otherwise returns nil.
X * Treats keywords as identifiers.
X */
X{
X	register const char *name;
X        if (nexttok==ID) {
X                name = NextString;
X                nextlex();
X                return name;
X        } else  return nil;
X}
X
X
Xstruct hshentry * getnum()
X/* Function: Checks if nexttok is a number. If so,
X * advances the input by calling nextlex and returns a pointer
X * to the hashtable entry. Otherwise returns nil.
X * Doesn't work if hshenter is false.
X */
X{
X        register struct hshentry * num;
X        if (nexttok==NUM) {
X                num=nexthsh;
X                nextlex();
X                return num;
X        } else  return nil;
X}
X
X	struct cbuf
Xgetphrases(key)
X	const char *key;
X/* Get a series of phrases that do not start with KEY, yield resulting buffer.
X * Stop when the next phrase starts with a token that is not an identifier,
X * or is KEY.
X * Assume foutptr == NULL.
X */
X{
X    register FILE *fin;
X    register int c;
X    register char *p;
X    const char *lim;
X    register const char *ki, *kn;
X    struct cbuf r;
X    struct buf b;
X
X    if (nexttok!=ID  ||  strcmp(NextString,key) == 0) {
X	r.string = 0;
X	r.size = 0;
X	return r;
X    } else {
X	warnignore();
X	fin = finptr;
X	bufautobegin(&b);
X	bufscpy(&b, NextString);
X	ffree1(NextString);
X	p = b.string + strlen(b.string);
X	lim = b.string + b.size;
X	c = nextc;
X	for (;;) {
X	    for (;;) {
X		if (lim <= p)
X		    p = bufenlarge(&b, &lim);
X		*p++ = c;
X		switch (ctab[c]) {
X		    default:
X			fatserror("unknown character `%c'", c);
X			/*NOTREACHED*/
X		    case EOFILE:
X			unexpectedEOF();
X			/*NOTREACHED*/
X		    case NEWLN:
X			++rcsline;
X			/* fall into */
X		    case COLON: case DIGIT: case LETTER: case Letter:
X		    case PERIOD: case SPACE:
X			c = getc(fin);
X			continue;
X		    case SBEGIN: /* long string */
X			for (;;) {
X			    for (;;) {
X				c = getc(fin);
X				if (lim <= p)
X				    p = bufenlarge(&b, &lim);
X				*p++ = c;
X				switch (c) {
X				    case EOF:
X					unexpectedEOF();
X					/*NOTREACHED*/
X				    case '\n':
X					++rcsline;
X					/* fall into */
X				    default:
X					continue;
X				    case SDELIM:
X					break;
X				}
X				break;
X			    }
X			    c = getc(fin);
X			    if (c != SDELIM)
X				break;
X			    if (lim <= p)
X				p = bufenlarge(&b, &lim);
X			    *p++ = c;
X			}
X			continue;
X		    case SEMI:
X			c = getc(fin);
X			if (ctab[c] == NEWLN) {
X			    ++rcsline;
X			    if (lim <= p)
X				p = bufenlarge(&b, &lim);
X			    *p++ = c;
X			    c = getc(fin);
X			}
X			for (;; c = getc(fin)) {
X			    switch (ctab[c]) {
X				case NEWLN: ++rcsline; continue;
X				case SPACE: continue;
X				default: break;
X			    }
X			    break;
X			}
X			break;
X		}
X		break;
X	    }
X	    switch (ctab[c]) {
X		case LETTER:
X		case Letter:
X		    for (kn = key;  c && *kn==c;  kn++)
X			if ((c = getc(fin)) == EOF)
X			    unexpectedEOF();
X		    if (!*kn)
X			switch (ctab[c]) {
X			    case DIGIT: case LETTER: case Letter:
X				break;
X			    default:
X				nextc = c;
X				NextString = fstrsave(key);
X				nexttok = ID;
X				goto returnit;
X			}
X		    for (ki=key; ki<kn; ) {
X			if (lim <= p)
X			    p = bufenlarge(&b, &lim);
X			*p++ = *ki++;
X		    }
X		    break;
X		default:
X		    nextc = c;
X		    nextlex();
X		    goto returnit;
X	    }
X	}
X
X    returnit:
X	/*
X	 * Do the following instead of bufautoend(&b),
X	 * because the buffer must survive until we are done with the file.
X	 */
X	r.size = p - b.string;
X	r.string = (char*)fremember(testrealloc((malloc_type)b.string, r.size));
X	return r;
X    }
X}
X
X
X	void
Xreadstring()
X/* skip over characters until terminating single SDELIM        */
X/* If foutptr is set, copy every character read to foutptr.    */
X/* Does not advance nextlex at the end.                        */
X{       register c;
X	register FILE * fin,  * frew;
X	fin=finptr; frew=foutptr;
X	if (frew) {
X		/* Copy string verbatim to foutptr.  */
X                while ((c=getc(fin)) != EOF) {
X			aputc(c,frew);
X			switch (c) {
X			    case '\n':
X				++rcsline;
X				break;
X			    case SDELIM:
X				if ((c=getc(fin)) == EOF) {
X					nextc=c;
X					return;
X				}
X				aputc(c,frew);
X				if (c != SDELIM) {
X                                        /* end of string */
X                                        nextc=c;
X                                        return;
X                                }
X				break;
X                        }
X                }
X        } else {
X                /* skip string */
X                while ((c=getc(fin)) != EOF) {
X			switch (c) {
X			    case '\n':
X				++rcsline;
X				break;
X			    case SDELIM:
X                                if ((c=getc(fin)) != SDELIM) {
X                                        /* end of string */
X                                        nextc=c;
X                                        return;
X                                }
X				break;
X                        }
X                }
X        }
X	unterminatedString();
X}
X
X
X	void
Xprintstring()
X/* Function: copy a string to stdout, until terminated with a single SDELIM.
X * Does not advance nextlex at the end.
X */
X{
X        register c;
X	register FILE *fin, *fout;
X	fin=finptr;
X	fout = stdout;
X	while ((c=getc(fin)) != EOF) {
X		switch (c) {
X		    case '\n':
X			++rcsline;
X			break;
X		    case SDELIM:
X			if ((c=getc(fin)) != SDELIM) {
X                                /* end of string */
X                                nextc=c;
X                                return;
X                        }
X			break;
X                }
X		aputc(c,fout);
X        }
X	unterminatedString();
X}
X
X
X
X	struct cbuf
Xsavestring(target)
X	struct buf *target;
X/* Copies a string terminated with SDELIM from file finptr to buffer target.
X * Double SDELIM is replaced with SDELIM.
X * If foutptr is set, the string is also copied unchanged to foutptr.
X * Does not advance nextlex at the end.
X * Yield a copy of *TARGET, except with exact length.
X */
X{
X        register c;
X	register FILE * fin, * frew;
X	register char *tp;
X	const char *lim;
X	struct cbuf r;
X
X	fin=finptr; frew=foutptr;
X	tp = target->string;  lim = tp + target->size;
X	for (;;) {
X		GETC(fin,frew,c);
X		switch (c) {
X		    case '\n':
X			++rcsline;
X			break;
X		    case SDELIM:
X			GETC(fin,frew,c);
X			if (c != SDELIM) {
X                                /* end of string */
X                                nextc=c;
X				r.string = target->string;
X				r.size = tp - r.string;
X				return r;
X                        }
X			break;
X		    case EOF:
X			unterminatedString();
X                }
X		if (tp == lim)
X			tp = bufenlarge(target, &lim);
X		*tp++ = c;
X        }
X}
X
X
X	char *
Xcheckid(id, delimiter)
X	register char *id;
X	int delimiter;
X/*   Function:  check whether the string starting at id is an   */
X/*		identifier and return a pointer to the delimiter*/
X/*		after the identifier.  White space, delim and 0 */
X/*              are legal delimiters.  Aborts the program if not*/
X/*              a legal identifier. Useful for checking commands*/
X/*		If !delim, the only delimiter is 0.		*/
X{
X        register enum  tokens  d;
X        register char    *temp;
X        register char    c,tc;
X	register char delim = delimiter;
X
X	temp = id;
X	if ((d = ctab[(unsigned char)(c = *id)])==LETTER || d==Letter) {
X	    while ((d = ctab[(unsigned char)(c = *++id)])==LETTER
X		|| d==Letter || d==DIGIT || d==IDCHAR
X	    )
X		;
X	    if (c  &&  (!delim || c!=delim && c!=' ' && c!='\t' && c!='\n')) {
X                /* append \0 to end of id before error message */
X                tc = c;
X                while( (c=(*++id))!=' ' && c!='\t' && c!='\n' && c!='\0' && c!=delim) ;
X                *id = '\0';
X		faterror("invalid character %c in identifier `%s'",tc,temp);
X	    }
X        } else {
X            /* append \0 to end of id before error message */
X            while( (c=(*++id))!=' ' && c!='\t' && c!='\n' && c!='\0' && c!=delim) ;
X            *id = '\0';
X	    faterror("identifier `%s' doesn't start with letter", temp);
X        }
X	return id;
X}
X
X	void
Xchecksid(id)
X	register char *id;
X/* Check whether the string ID is an identifier.  */
X{
X	VOID checkid(id, 0);
X}
X
X
X	exiting void
XIOerror()
X{
X	static looping;
X	if (looping)
X		exiterr();
X	looping = true;
X	faterror("input/output error; is the file system full?");
X}
X
Xvoid eflush() { if (fflush(stderr) == EOF) IOerror(); }
Xvoid oflush() { if (fflush(stdout) == EOF) IOerror(); }
X
Xexiting void unterminatedString() { fatserror("unterminated string"); }
X
X	static exiting void
Xfatcleanup(already_newline)
X	int already_newline;
X{
X	VOID fprintf(stderr, already_newline+"\n%s aborted\n", cmdid);
X	exiterr();
X}
X
Xstatic void errsay() { oflush(); aprintf(stderr,"%s error: ",cmdid); nerror++; }
Xstatic void fatsay() { oflush(); VOID fprintf(stderr,"%s error: ",cmdid); }
X
Xvoid eerror(n) const char *n; { errsay(); perror(n); eflush(); }
Xexiting void efaterror(n) const char *n; { fatsay(); perror(n); fatcleanup(true); }
X
X#if has_prototypes
X	void
Xerror(const char *format,...)
X#else
X	/*VARARGS1*/ void error(format, va_alist) const char *format; va_dcl
X#endif
X/* non-fatal error */
X{
X	va_list args;
X	errsay();
X	vararg_start(args, format);
X	fvfprintf(stderr, format, args);
X	va_end(args);
X	afputc('\n',stderr);
X	eflush();
X}
X
X#if has_prototypes
X	exiting void
Xfatserror(const char *format,...)
X#else
X	/*VARARGS1*/ exiting void
X	fatserror(format, va_alist) const char *format; va_dcl
X#endif
X/* fatal syntax error */
X{
X	va_list args;
X	oflush();
X	VOID fprintf(stderr, "%s: %s:%lu: ", cmdid, RCSfilename, rcsline);
X	vararg_start(args, format);
X	fvfprintf(stderr, format, args);
X	va_end(args);
X	fatcleanup(false);
X}
X
X#if has_prototypes
X	exiting void
Xfaterror(const char *format,...)
X#else
X	/*VARARGS1*/ exiting void faterror(format, va_alist)
X	const char *format; va_dcl
X#endif
X/* fatal error, terminates program after cleanup */
X{
X	va_list args;
X	fatsay();
X	vararg_start(args, format);
X	fvfprintf(stderr, format, args);
X	va_end(args);
X	fatcleanup(false);
X}
X
X#if has_prototypes
X	void
Xwarn(const char *format,...)
X#else
X	/*VARARGS1*/ void warn(format, va_alist) const char *format; va_dcl
X#endif
X/* prints a warning message */
X{
X	va_list args;
X	oflush();
X	aprintf(stderr,"%s warning: ",cmdid);
X	vararg_start(args, format);
X	fvfprintf(stderr, format, args);
X	va_end(args);
X	afputc('\n',stderr);
X	eflush();
X}
X
X	void
Xredefined(c)
X	int c;
X{
X	warn("redefinition of -%c option", c);
X}
X
X#if has_prototypes
X	void
Xdiagnose(const char *format,...)
X#else
X	/*VARARGS1*/ void diagnose(format, va_alist) const char *format; va_dcl
X#endif
X/* prints a diagnostic message */
X/* Unlike the other routines, it does not append a newline. */
X/* This lets some callers suppress the newline, and is faster */
X/* in implementations that flush stderr just at the end of each printf. */
X{
X	va_list args;
X        if (!quietflag) {
X		oflush();
X		vararg_start(args, format);
X		fvfprintf(stderr, format, args);
X		va_end(args);
X		eflush();
X        }
X}
X
X
X
X	void
Xafputc(c, f)
X/* Function: afputc(c,f) acts like aputc(c,f), but is smaller and slower.
X */
X	int c;
X	register FILE *f;
X{
X	aputc(c,f);
X}
X
X
X	void
Xaputs(s, iop)
X	const char *s;
X	FILE *iop;
X/* Function: Put string s on file iop, abort on error.
X */
X{
X	if (fputs(s, iop) == EOF)
X		IOerror();
X}
X
X
X
X	void
X#if has_prototypes
Xfvfprintf(FILE *stream, const char *format, va_list args)
X#else
X	fvfprintf(stream,format,args) FILE *stream; char *format; va_list args;
X#endif
X/* like vfprintf, except abort program on error */
X{
X#if has_vfprintf
X	if (vfprintf(stream, format, args) == EOF)
X#else
X	_doprnt(format, args, stream);
X	if (ferror(stream))
X#endif
X		IOerror();
X}
X
X#if has_prototypes
X	void
Xaprintf(FILE *iop, const char *fmt, ...)
X#else
X	/*VARARGS2*/ void
Xaprintf(iop, fmt, va_alist)
XFILE *iop;
Xconst char *fmt;
Xva_dcl
X#endif
X/* Function: formatted output. Same as fprintf in stdio,
X * but aborts program on error
X */
X{
X	va_list ap;
X	vararg_start(ap, fmt);
X	fvfprintf(iop, fmt, ap);
X	va_end(ap);
X}
X
X
X
X#ifdef LEXDB
X/* test program reading a stream of lexemes and printing the tokens.
X */
X
X
X
X	int
Xmain(argc,argv)
Xint argc; char * argv[];
X{
X        cmdid="lextest";
X        if (argc<2) {
X		aputs("No input file\n",stderr);
X		exitmain(EXIT_FAILURE);
X        }
X        if ((finptr=fopen(argv[1], "r")) == NULL) {
X		faterror("can't open input file %s",argv[1]);
X        }
X        Lexinit();
X        while (nexttok != EOFILE) {
X        switch (nexttok) {
X
X        case ID:
X                VOID printf("ID: %s",NextString);
X                break;
X
X        case NUM:
X		if (hshenter)
X                   VOID printf("NUM: %s, index: %d",nexthsh->num, nexthsh-hshtab);
X                else
X                   VOID printf("NUM, unentered: %s",NextString);
X                hshenter = !hshenter; /*alternate between dates and numbers*/
X                break;
X
X        case COLON:
X                VOID printf("COLON"); break;
X
X        case SEMI:
X                VOID printf("SEMI"); break;
X
X        case STRING:
X                readstring();
X                VOID printf("STRING"); break;
X
X        case UNKN:
X                VOID printf("UNKN"); break;
X
X        default:
X                VOID printf("DEFAULT"); break;
X        }
X        VOID printf(" | ");
X        nextlex();
X        }
X        VOID printf("\nEnd of lexical analyzer test\n");
X	exitmain(EXIT_SUCCESS);
X}
X
Xexiting void exiterr() { _exit(EXIT_FAILURE); }
X
X
X#endif
END_OF_FILE
  if test 24012 -ne `wc -c <'src/rcslex.c'`; then
    echo shar: \"'src/rcslex.c'\" unpacked with wrong size!
  fi
  # end of 'src/rcslex.c'
fi
echo shar: End of archive 5 \(of 12\).
cp /dev/null ark5isdone
MISSING=""
for I in 1 2 3 4 5 6 7 8 9 10 11 12 ; do
    if test ! -f ark${I}isdone ; then
	MISSING="${MISSING} ${I}"
    fi
done
if test "${MISSING}" = "" ; then
    echo You have unpacked all 12 archives.
    rm -f ark[1-9]isdone ark[1-9][0-9]isdone
else
    echo You still must unpack the following archives:
    echo "        " ${MISSING}
fi
exit 0
exit 0 # Just in case...
-- 
Please send comp.sources.unix-related mail to rsalz@uunet.uu.net.
Use a domain-based address or give alternate paths, or you may lose out.