NetBSD/gnu/usr.bin/rcs/lib/rcssyn.c

688 lines
16 KiB
C
Raw Normal View History

1995-02-24 05:24:53 +03:00
/* RCS file syntactic analysis */
/******************************************************************************
1993-07-09 05:56:50 +04:00
* Syntax Analysis.
* Keyword table
* Testprogram: define SYNTEST
* Compatibility with Release 2: define COMPAT2=1
1995-02-24 05:24:53 +03:00
******************************************************************************
1993-07-09 05:56:50 +04:00
*/
1995-02-24 05:24:53 +03:00
/* Copyright 1982, 1988, 1989 Walter Tichy
Copyright 1990, 1991, 1992, 1993, 1994 Paul Eggert
1993-07-09 05:56:50 +04:00
Distributed under license by the Free Software Foundation, Inc.
This file is part of RCS.
RCS is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
RCS is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with RCS; see the file COPYING. If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
Report problems and direct all questions to:
rcs-bugs@cs.purdue.edu
*/
1995-02-24 05:24:53 +03:00
/*
* $Log: rcssyn.c,v $
* Revision 1.3 1995/02/24 02:25:14 mycroft
* RCS 5.6.7.4
*
* Revision 5.13 1994/03/20 04:52:58 eggert
* Remove lint.
*
* Revision 5.12 1993/11/03 17:42:27 eggert
* Parse MKS RCS dates; ignore \r in diff control lines.
* Don't discard ignored phrases. Improve quality of diagnostics.
*
* Revision 5.11 1992/07/28 16:12:44 eggert
* Avoid `unsigned'. Statement macro names now end in _.
*
* Revision 5.10 1992/01/24 18:44:19 eggert
* Move put routines to rcsgen.c.
*
* Revision 5.9 1992/01/06 02:42:34 eggert
* ULONG_MAX/10 -> ULONG_MAX_OVER_10
* while (E) ; -> while (E) continue;
*
* Revision 5.8 1991/08/19 03:13:55 eggert
* Tune.
*
* Revision 5.7 1991/04/21 11:58:29 eggert
* Disambiguate names on shortname hosts.
* Fix errno bug. Add MS-DOS support.
*
* Revision 5.6 1991/02/28 19:18:51 eggert
* Fix null termination bug in reporting keyword expansion.
*
* Revision 5.5 1991/02/25 07:12:44 eggert
* Check diff output more carefully; avoid overflow.
*
* Revision 5.4 1990/11/01 05:28:48 eggert
* When ignoring unknown phrases, copy them to the output RCS file.
* Permit arbitrary data in logs and comment leaders.
* Don't check for nontext on initial checkin.
*
* Revision 5.3 1990/09/20 07:58:32 eggert
* Remove the test for non-text bytes; it caused more pain than it cured.
*
* Revision 5.2 1990/09/04 08:02:30 eggert
* Parse RCS files with no revisions.
* Don't strip leading white space from diff commands. Count RCS lines better.
*
* Revision 5.1 1990/08/29 07:14:06 eggert
* Add -kkvl. Clean old log messages too.
*
* Revision 5.0 1990/08/22 08:13:44 eggert
* Try to parse future RCS formats without barfing.
* Add -k. Don't require final newline.
* Remove compile-time limits; use malloc instead.
* Don't output branch keyword if there's no default branch,
* because RCS version 3 doesn't understand it.
* Tune. Remove lint.
* Add support for ISO 8859. Ansify and Posixate.
* Check that a newly checked-in file is acceptable as input to 'diff'.
* Check diff's output.
*
* Revision 4.6 89/05/01 15:13:32 narten
* changed copyright header to reflect current distribution rules
*
* Revision 4.5 88/08/09 19:13:21 eggert
* Allow cc -R; remove lint.
*
* Revision 4.4 87/12/18 11:46:16 narten
* more lint cleanups (Guy Harris)
*
* Revision 4.3 87/10/18 10:39:36 narten
* Updating version numbers. Changes relative to 1.1 actually relative to
* 4.1
*
* Revision 1.3 87/09/24 14:00:49 narten
* Sources now pass through lint (if you ignore printf/sprintf/fprintf
* warnings)
*
* Revision 1.2 87/03/27 14:22:40 jenkins
* Port to suns
*
* Revision 4.1 83/03/28 11:38:49 wft
* Added parsing and printing of default branch.
*
* Revision 3.6 83/01/15 17:46:50 wft
* Changed readdelta() to initialize selector and log-pointer.
* Changed puttree to check for selector==DELETE; putdtext() uses DELNUMFORM.
*
* Revision 3.5 82/12/08 21:58:58 wft
* renamed Commentleader to Commleader.
*
* Revision 3.4 82/12/04 13:24:40 wft
* Added routine gettree(), which updates keeplock after reading the
* delta tree.
*
* Revision 3.3 82/11/28 21:30:11 wft
* Reading and printing of Suffix removed; version COMPAT2 skips the
* Suffix for files of release 2 format. Fixed problems with printing nil.
*
* Revision 3.2 82/10/18 21:18:25 wft
* renamed putdeltatext to putdtext.
*
* Revision 3.1 82/10/11 19:45:11 wft
* made sure getc() returns into an integer.
*/
1993-07-09 05:56:50 +04:00
/* version COMPAT2 reads files of the format of release 2 and 3, but
* generates files of release 3 format. Need not be defined if no
* old RCS files generated with release 2 exist.
*/
#include "rcsbase.h"
1995-02-24 05:24:53 +03:00
libId(synId, "$Id: rcssyn.c,v 1.3 1995/02/24 02:25:14 mycroft Exp $")
1993-07-09 05:56:50 +04:00
static char const *getkeyval P((char const*,enum tokens,int));
1995-02-24 05:24:53 +03:00
static int getdelta P((void));
1993-07-09 05:56:50 +04:00
static int strn2expmode P((char const*,size_t));
1995-02-24 05:24:53 +03:00
static struct hshentry *getdnum P((void));
static void badDiffOutput P((char const*)) exiting;
static void diffLineNumberTooLarge P((char const*)) exiting;
static void getsemi P((char const*));
1993-07-09 05:56:50 +04:00
/* keyword table */
char const
Kaccess[] = "access",
Kauthor[] = "author",
Kbranch[] = "branch",
Kcomment[] = "comment",
Kdate[] = "date",
1995-02-24 05:24:53 +03:00
Kdesc[] = "desc",
1993-07-09 05:56:50 +04:00
Kexpand[] = "expand",
Khead[] = "head",
Klocks[] = "locks",
1995-02-24 05:24:53 +03:00
Klog[] = "log",
1993-07-09 05:56:50 +04:00
Knext[] = "next",
Kstate[] = "state",
Kstrict[] = "strict",
1995-02-24 05:24:53 +03:00
Ksymbols[] = "symbols",
Ktext[] = "text";
static char const
1993-07-09 05:56:50 +04:00
#if COMPAT2
Ksuffix[] = "suffix",
#endif
1995-02-24 05:24:53 +03:00
K_branches[]= "branches";
1993-07-09 05:56:50 +04:00
static struct buf Commleader;
struct cbuf Comment;
1995-02-24 05:24:53 +03:00
struct cbuf Ignored;
1993-07-09 05:56:50 +04:00
struct access * AccessList;
struct assoc * Symbols;
struct lock * Locks;
int Expand;
int StrictLocks;
struct hshentry * Head;
char const * Dbranch;
1995-02-24 05:24:53 +03:00
int TotalDeltas;
1993-07-09 05:56:50 +04:00
static void
getsemi(key)
char const *key;
/* Get a semicolon to finish off a phrase started by KEY. */
{
if (!getlex(SEMI))
fatserror("missing ';' after '%s'", key);
}
static struct hshentry *
getdnum()
/* Get a delta number. */
{
register struct hshentry *delta = getnum();
if (delta && countnumflds(delta->num)&1)
fatserror("%s isn't a delta number", delta->num);
return delta;
}
void
getadmin()
/* Read an <admin> and initialize the appropriate global variables. */
{
register char const *id;
struct access * newaccess;
struct assoc * newassoc;
struct lock * newlock;
struct hshentry * delta;
struct access **LastAccess;
struct assoc **LastSymbol;
struct lock **LastLock;
struct buf b;
struct cbuf cb;
TotalDeltas=0;
getkey(Khead);
Head = getdnum();
getsemi(Khead);
1995-02-24 05:24:53 +03:00
Dbranch = 0;
1993-07-09 05:56:50 +04:00
if (getkeyopt(Kbranch)) {
if ((delta = getnum()))
Dbranch = delta->num;
getsemi(Kbranch);
}
#if COMPAT2
/* read suffix. Only in release 2 format */
if (getkeyopt(Ksuffix)) {
if (nexttok==STRING) {
readstring(); nextlex(); /* Throw away the suffix. */
} else if (nexttok==ID) {
nextlex();
}
getsemi(Ksuffix);
}
#endif
getkey(Kaccess);
LastAccess = &AccessList;
1995-02-24 05:24:53 +03:00
while ((id = getid())) {
1993-07-09 05:56:50 +04:00
newaccess = ftalloc(struct access);
newaccess->login = id;
*LastAccess = newaccess;
LastAccess = &newaccess->nextaccess;
}
1995-02-24 05:24:53 +03:00
*LastAccess = 0;
1993-07-09 05:56:50 +04:00
getsemi(Kaccess);
getkey(Ksymbols);
LastSymbol = &Symbols;
1995-02-24 05:24:53 +03:00
while ((id = getid())) {
1993-07-09 05:56:50 +04:00
if (!getlex(COLON))
fatserror("missing ':' in symbolic name definition");
if (!(delta=getnum())) {
fatserror("missing number in symbolic name definition");
} else { /*add new pair to association list*/
newassoc = ftalloc(struct assoc);
newassoc->symbol=id;
newassoc->num = delta->num;
*LastSymbol = newassoc;
LastSymbol = &newassoc->nextassoc;
}
}
1995-02-24 05:24:53 +03:00
*LastSymbol = 0;
1993-07-09 05:56:50 +04:00
getsemi(Ksymbols);
getkey(Klocks);
LastLock = &Locks;
1995-02-24 05:24:53 +03:00
while ((id = getid())) {
1993-07-09 05:56:50 +04:00
if (!getlex(COLON))
fatserror("missing ':' in lock");
if (!(delta=getdnum())) {
fatserror("missing number in lock");
} else { /*add new pair to lock list*/
newlock = ftalloc(struct lock);
newlock->login=id;
newlock->delta=delta;
*LastLock = newlock;
LastLock = &newlock->nextlock;
}
}
1995-02-24 05:24:53 +03:00
*LastLock = 0;
1993-07-09 05:56:50 +04:00
getsemi(Klocks);
if ((StrictLocks = getkeyopt(Kstrict)))
getsemi(Kstrict);
1995-02-24 05:24:53 +03:00
clear_buf(&Comment);
1993-07-09 05:56:50 +04:00
if (getkeyopt(Kcomment)) {
if (nexttok==STRING) {
Comment = savestring(&Commleader);
nextlex();
}
getsemi(Kcomment);
}
Expand = KEYVAL_EXPAND;
if (getkeyopt(Kexpand)) {
if (nexttok==STRING) {
bufautobegin(&b);
cb = savestring(&b);
if ((Expand = strn2expmode(cb.string,cb.size)) < 0)
fatserror("unknown expand mode %.*s",
(int)cb.size, cb.string
);
bufautoend(&b);
nextlex();
}
getsemi(Kexpand);
}
Ignored = getphrases(Kdesc);
}
char const *const expand_names[] = {
/* These must agree with *_EXPAND in rcsbase.h. */
"kv","kvl","k","v","o",
0
};
int
str2expmode(s)
char const *s;
/* Yield expand mode corresponding to S, or -1 if bad. */
{
return strn2expmode(s, strlen(s));
}
static int
strn2expmode(s, n)
char const *s;
size_t n;
{
char const *const *p;
for (p = expand_names; *p; ++p)
if (memcmp(*p,s,n) == 0 && !(*p)[n])
return p - expand_names;
return -1;
}
void
1995-02-24 05:24:53 +03:00
ignorephrases(key)
const char *key;
/*
* Ignore a series of phrases that do not start with KEY.
* Stop when the next phrase starts with a token that is not an identifier,
* or is KEY.
*/
1993-07-09 05:56:50 +04:00
{
for (;;) {
1995-02-24 05:24:53 +03:00
nextlex();
if (nexttok != ID || strcmp(NextString,key) == 0)
break;
warnignore();
hshenter=false;
for (;; nextlex()) {
switch (nexttok) {
case SEMI: hshenter=true; break;
case ID:
case NUM: ffree1(NextString); continue;
case STRING: readstring(); continue;
default: continue;
}
break;
}
1993-07-09 05:56:50 +04:00
}
}
static int
getdelta()
/* Function: reads a delta block.
* returns false if the current block does not start with a number.
*/
{
register struct hshentry * Delta, * num;
struct branchhead **LastBranch, *NewBranch;
if (!(Delta = getdnum()))
return false;
hshenter = false; /*Don't enter dates into hashtable*/
1995-02-24 05:24:53 +03:00
{
char const *date = getkeyval(Kdate, NUM, false);
/* Strip any leading "19" from dates generated by MKS RCS. */
Delta->date = date + 2*(
date[0] == '1' &&
date[1] == '9' &&
isdigit(date[2]) &&
isdigit(date[3]) &&
date[4] == '.'
);
}
1993-07-09 05:56:50 +04:00
hshenter=true; /*reset hshenter for revision numbers.*/
Delta->author = getkeyval(Kauthor, ID, false);
Delta->state = getkeyval(Kstate, ID, true);
getkey(K_branches);
LastBranch = &Delta->branches;
while ((num = getdnum())) {
NewBranch = ftalloc(struct branchhead);
NewBranch->hsh = num;
*LastBranch = NewBranch;
LastBranch = &NewBranch->nextbranch;
}
1995-02-24 05:24:53 +03:00
*LastBranch = 0;
1993-07-09 05:56:50 +04:00
getsemi(K_branches);
getkey(Knext);
Delta->next = num = getdnum();
getsemi(Knext);
1995-02-24 05:24:53 +03:00
Delta->lockedby = 0;
1993-07-09 05:56:50 +04:00
Delta->log.string = 0;
Delta->selector = true;
Delta->ig = getphrases(Kdesc);
TotalDeltas++;
return (true);
}
void
gettree()
/* Function: Reads in the delta tree with getdelta(), then
* updates the lockedby fields.
*/
{
struct lock const *currlock;
1995-02-24 05:24:53 +03:00
while (getdelta())
continue;
1993-07-09 05:56:50 +04:00
currlock=Locks;
while (currlock) {
currlock->delta->lockedby = currlock->login;
currlock = currlock->nextlock;
}
}
void
getdesc(prdesc)
int prdesc;
/* Function: read in descriptive text
* nexttok is not advanced afterwards.
* If prdesc is set, the text is printed to stdout.
*/
{
getkeystring(Kdesc);
if (prdesc)
printstring(); /*echo string*/
else readstring(); /*skip string*/
}
static char const *
getkeyval(keyword, token, optional)
char const *keyword;
enum tokens token;
int optional;
/* reads a pair of the form
* <keyword> <token> ;
* where token is one of <id> or <num>. optional indicates whether
* <token> is optional. A pointer to
* the actual character string of <id> or <num> is returned.
*/
{
1995-02-24 05:24:53 +03:00
register char const *val = 0;
1993-07-09 05:56:50 +04:00
getkey(keyword);
if (nexttok==token) {
val = NextString;
nextlex();
} else {
if (!optional)
fatserror("missing %s", keyword);
}
getsemi(keyword);
return(val);
}
void
unexpected_EOF()
{
1995-02-24 05:24:53 +03:00
rcsfaterror("unexpected EOF in diff output");
1993-07-09 05:56:50 +04:00
}
void
initdiffcmd(dc)
register struct diffcmd *dc;
/* Initialize *dc suitably for getdiffcmd(). */
{
dc->adprev = 0;
dc->dafter = 0;
}
1995-02-24 05:24:53 +03:00
static void
1993-07-09 05:56:50 +04:00
badDiffOutput(buf)
char const *buf;
{
1995-02-24 05:24:53 +03:00
rcsfaterror("bad diff output line: %s", buf);
1993-07-09 05:56:50 +04:00
}
1995-02-24 05:24:53 +03:00
static void
1993-07-09 05:56:50 +04:00
diffLineNumberTooLarge(buf)
char const *buf;
{
1995-02-24 05:24:53 +03:00
rcsfaterror("diff line number too large: %s", buf);
1993-07-09 05:56:50 +04:00
}
int
getdiffcmd(finfile, delimiter, foutfile, dc)
RILE *finfile;
FILE *foutfile;
int delimiter;
struct diffcmd *dc;
/* Get a editing command output by 'diff -n' from fin.
* The input is delimited by SDELIM if delimiter is set, EOF otherwise.
* Copy a clean version of the command to fout (if nonnull).
* Yield 0 for 'd', 1 for 'a', and -1 for EOF.
* Store the command's line number and length into dc->line1 and dc->nlines.
* Keep dc->adprev and dc->dafter up to date.
*/
{
register int c;
declarecache;
register FILE *fout;
register char *p;
register RILE *fin;
1995-02-24 05:24:53 +03:00
long line1, nlines, t;
1993-07-09 05:56:50 +04:00
char buf[BUFSIZ];
fin = finfile;
fout = foutfile;
setupcache(fin); cache(fin);
1995-02-24 05:24:53 +03:00
cachegeteof_(c, { if (delimiter) unexpected_EOF(); return -1; } )
1993-07-09 05:56:50 +04:00
if (delimiter) {
if (c==SDELIM) {
1995-02-24 05:24:53 +03:00
cacheget_(c)
1993-07-09 05:56:50 +04:00
if (c==SDELIM) {
buf[0] = c;
buf[1] = 0;
badDiffOutput(buf);
}
uncache(fin);
nextc = c;
if (fout)
aprintf(fout, "%c%c", SDELIM, c);
return -1;
}
}
p = buf;
do {
if (buf+BUFSIZ-2 <= p) {
1995-02-24 05:24:53 +03:00
rcsfaterror("diff output command line too long");
1993-07-09 05:56:50 +04:00
}
*p++ = c;
1995-02-24 05:24:53 +03:00
cachegeteof_(c, unexpected_EOF();)
1993-07-09 05:56:50 +04:00
} while (c != '\n');
uncache(fin);
if (delimiter)
++rcsline;
*p = '\0';
for (p = buf+1; (c = *p++) == ' '; )
1995-02-24 05:24:53 +03:00
continue;
1993-07-09 05:56:50 +04:00
line1 = 0;
while (isdigit(c)) {
if (
1995-02-24 05:24:53 +03:00
LONG_MAX/10 < line1 ||
(t = line1 * 10, (line1 = t + (c - '0')) < t)
1993-07-09 05:56:50 +04:00
)
diffLineNumberTooLarge(buf);
c = *p++;
}
while (c == ' ')
c = *p++;
nlines = 0;
while (isdigit(c)) {
if (
1995-02-24 05:24:53 +03:00
LONG_MAX/10 < nlines ||
(t = nlines * 10, (nlines = t + (c - '0')) < t)
1993-07-09 05:56:50 +04:00
)
diffLineNumberTooLarge(buf);
c = *p++;
}
1995-02-24 05:24:53 +03:00
if (c == '\r')
c = *p++;
1993-07-09 05:56:50 +04:00
if (c || !nlines) {
badDiffOutput(buf);
}
if (line1+nlines < line1)
diffLineNumberTooLarge(buf);
switch (buf[0]) {
case 'a':
if (line1 < dc->adprev) {
1995-02-24 05:24:53 +03:00
rcsfaterror("backward insertion in diff output: %s", buf);
1993-07-09 05:56:50 +04:00
}
dc->adprev = line1 + 1;
break;
case 'd':
if (line1 < dc->adprev || line1 < dc->dafter) {
1995-02-24 05:24:53 +03:00
rcsfaterror("backward deletion in diff output: %s", buf);
1993-07-09 05:56:50 +04:00
}
dc->adprev = line1;
dc->dafter = line1 + nlines;
break;
default:
badDiffOutput(buf);
}
if (fout) {
aprintf(fout, "%s\n", buf);
}
dc->line1 = line1;
dc->nlines = nlines;
return buf[0] == 'a';
}
#ifdef SYNTEST
1995-02-24 05:24:53 +03:00
/* Input an RCS file and print its internal data structures. */
1993-07-09 05:56:50 +04:00
char const cmdid[] = "syntest";
int
main(argc,argv)
int argc; char * argv[];
{
if (argc<2) {
aputs("No input file\n",stderr);
exitmain(EXIT_FAILURE);
}
if (!(finptr = Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
faterror("can't open input file %s", argv[1]);
}
Lexinit();
getadmin();
1995-02-24 05:24:53 +03:00
fdlock = STDOUT_FILENO;
putadmin();
1993-07-09 05:56:50 +04:00
gettree();
getdesc(true);
nextlex();
if (!eoflex()) {
fatserror("expecting EOF");
}
exitmain(EXIT_SUCCESS);
}
1995-02-24 05:24:53 +03:00
void exiterr() { _exit(EXIT_FAILURE); }
1993-07-09 05:56:50 +04:00
#endif