1 /* RCS file syntactic analysis */
2 
3 /******************************************************************************
4  *                       Syntax Analysis.
5  *                       Keyword table
6  *                       Testprogram: define SYNTEST
7  *                       Compatibility with Release 2: define COMPAT2=1
8  ******************************************************************************
9  */
10 
11 /* Copyright 1982, 1988, 1989 Walter Tichy
12    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
13    Distributed under license by the Free Software Foundation, Inc.
14 
15 This file is part of RCS.
16 
17 RCS is free software; you can redistribute it and/or modify
18 it under the terms of the GNU General Public License as published by
19 the Free Software Foundation; either version 2, or (at your option)
20 any later version.
21 
22 RCS is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
25 GNU General Public License for more details.
26 
27 You should have received a copy of the GNU General Public License
28 along with RCS; see the file COPYING.
29 If not, write to the Free Software Foundation,
30 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
31 
32 Report problems and direct all questions to:
33 
34     rcs-bugs@cs.purdue.edu
35 
36 */
37 
38 /*
39  * $Log: rcssyn.c,v $
40  * Revision 5.15  1995/06/16 06:19:24  eggert
41  * Update FSF address.
42  *
43  * Revision 5.14  1995/06/01 16:23:43  eggert
44  * (expand_names): Add "b" for -kb.
45  * (getdelta): Don't strip leading "19" from MKS RCS dates; see cmpdate.
46  *
47  * Revision 5.13  1994/03/20 04:52:58  eggert
48  * Remove lint.
49  *
50  * Revision 5.12  1993/11/03 17:42:27  eggert
51  * Parse MKS RCS dates; ignore \r in diff control lines.
52  * Don't discard ignored phrases.  Improve quality of diagnostics.
53  *
54  * Revision 5.11  1992/07/28  16:12:44  eggert
55  * Avoid `unsigned'.  Statement macro names now end in _.
56  *
57  * Revision 5.10  1992/01/24  18:44:19  eggert
58  * Move put routines to rcsgen.c.
59  *
60  * Revision 5.9  1992/01/06  02:42:34  eggert
61  * ULONG_MAX/10 -> ULONG_MAX_OVER_10
62  * while (E) ; -> while (E) continue;
63  *
64  * Revision 5.8  1991/08/19  03:13:55  eggert
65  * Tune.
66  *
67  * Revision 5.7  1991/04/21  11:58:29  eggert
68  * Disambiguate names on shortname hosts.
69  * Fix errno bug.  Add MS-DOS support.
70  *
71  * Revision 5.6  1991/02/28  19:18:51  eggert
72  * Fix null termination bug in reporting keyword expansion.
73  *
74  * Revision 5.5  1991/02/25  07:12:44  eggert
75  * Check diff output more carefully; avoid overflow.
76  *
77  * Revision 5.4  1990/11/01  05:28:48  eggert
78  * When ignoring unknown phrases, copy them to the output RCS file.
79  * Permit arbitrary data in logs and comment leaders.
80  * Don't check for nontext on initial checkin.
81  *
82  * Revision 5.3  1990/09/20  07:58:32  eggert
83  * Remove the test for non-text bytes; it caused more pain than it cured.
84  *
85  * Revision 5.2  1990/09/04  08:02:30  eggert
86  * Parse RCS files with no revisions.
87  * Don't strip leading white space from diff commands.  Count RCS lines better.
88  *
89  * Revision 5.1  1990/08/29  07:14:06  eggert
90  * Add -kkvl.  Clean old log messages too.
91  *
92  * Revision 5.0  1990/08/22  08:13:44  eggert
93  * Try to parse future RCS formats without barfing.
94  * Add -k.  Don't require final newline.
95  * Remove compile-time limits; use malloc instead.
96  * Don't output branch keyword if there's no default branch,
97  * because RCS version 3 doesn't understand it.
98  * Tune.  Remove lint.
99  * Add support for ISO 8859.  Ansify and Posixate.
100  * Check that a newly checked-in file is acceptable as input to 'diff'.
101  * Check diff's output.
102  *
103  * Revision 4.6  89/05/01  15:13:32  narten
104  * changed copyright header to reflect current distribution rules
105  *
106  * Revision 4.5  88/08/09  19:13:21  eggert
107  * Allow cc -R; remove lint.
108  *
109  * Revision 4.4  87/12/18  11:46:16  narten
110  * more lint cleanups (Guy Harris)
111  *
112  * Revision 4.3  87/10/18  10:39:36  narten
113  * Updating version numbers. Changes relative to 1.1 actually relative to
114  * 4.1
115  *
116  * Revision 1.3  87/09/24  14:00:49  narten
117  * Sources now pass through lint (if you ignore printf/sprintf/fprintf
118  * warnings)
119  *
120  * Revision 1.2  87/03/27  14:22:40  jenkins
121  * Port to suns
122  *
123  * Revision 4.1  83/03/28  11:38:49  wft
124  * Added parsing and printing of default branch.
125  *
126  * Revision 3.6  83/01/15  17:46:50  wft
127  * Changed readdelta() to initialize selector and log-pointer.
128  * Changed puttree to check for selector==DELETE; putdtext() uses DELNUMFORM.
129  *
130  * Revision 3.5  82/12/08  21:58:58  wft
131  * renamed Commentleader to Commleader.
132  *
133  * Revision 3.4  82/12/04  13:24:40  wft
134  * Added routine gettree(), which updates keeplock after reading the
135  * delta tree.
136  *
137  * Revision 3.3  82/11/28  21:30:11  wft
138  * Reading and printing of Suffix removed; version COMPAT2 skips the
139  * Suffix for files of release 2 format. Fixed problems with printing nil.
140  *
141  * Revision 3.2  82/10/18  21:18:25  wft
142  * renamed putdeltatext to putdtext.
143  *
144  * Revision 3.1  82/10/11  19:45:11  wft
145  * made sure getc() returns into an integer.
146  */
147 
148 
149 
150 /* version COMPAT2 reads files of the format of release 2 and 3, but
151  * generates files of release 3 format. Need not be defined if no
152  * old RCS files generated with release 2 exist.
153  */
154 
155 #include "rcsbase.h"
156 
157 libId(synId, "$Id: rcssyn.c,v 5.15 1995/06/16 06:19:24 eggert Exp $")
158 
159 static char const *getkeyval P((char const*,enum tokens,int));
160 static int getdelta P((void));
161 static int strn2expmode P((char const*,size_t));
162 static struct hshentry *getdnum P((void));
163 static void badDiffOutput P((char const*)) exiting;
164 static void diffLineNumberTooLarge P((char const*)) exiting;
165 static void getsemi P((char const*));
166 
167 /* keyword table */
168 
169 char const
170 	Kaccess[]   = "access",
171 	Kauthor[]   = "author",
172 	Kbranch[]   = "branch",
173 	Kcomment[]  = "comment",
174 	Kdate[]     = "date",
175 	Kdesc[]     = "desc",
176 	Kexpand[]   = "expand",
177 	Khead[]     = "head",
178 	Klocks[]    = "locks",
179 	Klog[]      = "log",
180 	Knext[]     = "next",
181 	Kstate[]    = "state",
182 	Kstrict[]   = "strict",
183 	Ksymbols[]  = "symbols",
184 	Ktext[]     = "text";
185 
186 static char const
187 #if COMPAT2
188 	Ksuffix[]   = "suffix",
189 #endif
190 	K_branches[]= "branches";
191 
192 static struct buf Commleader;
193 struct cbuf Comment;
194 struct cbuf Ignored;
195 struct access   * AccessList;
196 struct assoc    * Symbols;
197 struct rcslock *Locks;
198 int		  Expand;
199 int               StrictLocks;
200 struct hshentry * Head;
201 char const      * Dbranch;
202 int TotalDeltas;
203 
204 
205 	static void
getsemi(key)206 getsemi(key)
207 	char const *key;
208 /* Get a semicolon to finish off a phrase started by KEY.  */
209 {
210 	if (!getlex(SEMI))
211 		fatserror("missing ';' after '%s'", key);
212 }
213 
214 	static struct hshentry *
getdnum()215 getdnum()
216 /* Get a delta number.  */
217 {
218 	register struct hshentry *delta = getnum();
219 	if (delta && countnumflds(delta->num)&1)
220 		fatserror("%s isn't a delta number", delta->num);
221 	return delta;
222 }
223 
224 
225 	void
getadmin()226 getadmin()
227 /* Read an <admin> and initialize the appropriate global variables.  */
228 {
229 	register char const *id;
230         struct access   * newaccess;
231         struct assoc    * newassoc;
232 	struct rcslock *newlock;
233         struct hshentry * delta;
234 	struct access **LastAccess;
235 	struct assoc **LastSymbol;
236 	struct rcslock **LastLock;
237 	struct buf b;
238 	struct cbuf cb;
239 
240         TotalDeltas=0;
241 
242 	getkey(Khead);
243 	Head = getdnum();
244 	getsemi(Khead);
245 
246 	Dbranch = 0;
247 	if (getkeyopt(Kbranch)) {
248 		if ((delta = getnum()))
249 			Dbranch = delta->num;
250 		getsemi(Kbranch);
251         }
252 
253 
254 #if COMPAT2
255         /* read suffix. Only in release 2 format */
256 	if (getkeyopt(Ksuffix)) {
257                 if (nexttok==STRING) {
258 			readstring(); nextlex(); /* Throw away the suffix.  */
259 		} else if (nexttok==ID) {
260                         nextlex();
261                 }
262 		getsemi(Ksuffix);
263         }
264 #endif
265 
266 	getkey(Kaccess);
267 	LastAccess = &AccessList;
268 	while ((id = getid())) {
269 		newaccess = ftalloc(struct access);
270                 newaccess->login = id;
271 		*LastAccess = newaccess;
272 		LastAccess = &newaccess->nextaccess;
273         }
274 	*LastAccess = 0;
275 	getsemi(Kaccess);
276 
277 	getkey(Ksymbols);
278 	LastSymbol = &Symbols;
279         while ((id = getid())) {
280                 if (!getlex(COLON))
281 			fatserror("missing ':' in symbolic name definition");
282                 if (!(delta=getnum())) {
283 			fatserror("missing number in symbolic name definition");
284                 } else { /*add new pair to association list*/
285 			newassoc = ftalloc(struct assoc);
286                         newassoc->symbol=id;
287 			newassoc->num = delta->num;
288 			*LastSymbol = newassoc;
289 			LastSymbol = &newassoc->nextassoc;
290                 }
291         }
292 	*LastSymbol = 0;
293 	getsemi(Ksymbols);
294 
295 	getkey(Klocks);
296 	LastLock = &Locks;
297         while ((id = getid())) {
298                 if (!getlex(COLON))
299 			fatserror("missing ':' in lock");
300 		if (!(delta=getdnum())) {
301 			fatserror("missing number in lock");
302                 } else { /*add new pair to lock list*/
303 			newlock = ftalloc(struct rcslock);
304                         newlock->login=id;
305                         newlock->delta=delta;
306 			*LastLock = newlock;
307 			LastLock = &newlock->nextlock;
308                 }
309         }
310 	*LastLock = 0;
311 	getsemi(Klocks);
312 
313 	if ((StrictLocks = getkeyopt(Kstrict)))
314 		getsemi(Kstrict);
315 
316 	clear_buf(&Comment);
317 	if (getkeyopt(Kcomment)) {
318 		if (nexttok==STRING) {
319 			Comment = savestring(&Commleader);
320 			nextlex();
321 		}
322 		getsemi(Kcomment);
323         }
324 
325 	Expand = KEYVAL_EXPAND;
326 	if (getkeyopt(Kexpand)) {
327 		if (nexttok==STRING) {
328 			bufautobegin(&b);
329 			cb = savestring(&b);
330 			if ((Expand = strn2expmode(cb.string,cb.size)) < 0)
331 			    fatserror("unknown expand mode %.*s",
332 				(int)cb.size, cb.string
333 			    );
334 			bufautoend(&b);
335 			nextlex();
336 		}
337 		getsemi(Kexpand);
338         }
339 	Ignored = getphrases(Kdesc);
340 }
341 
342 char const *const expand_names[] = {
343 	/* These must agree with *_EXPAND in rcsbase.h.  */
344 	"kv", "kvl", "k", "v", "o", "b",
345 	0
346 };
347 
348 	int
str2expmode(s)349 str2expmode(s)
350 	char const *s;
351 /* Yield expand mode corresponding to S, or -1 if bad.  */
352 {
353 	return strn2expmode(s, strlen(s));
354 }
355 
356 	static int
strn2expmode(s,n)357 strn2expmode(s, n)
358 	char const *s;
359 	size_t n;
360 {
361 	char const *const *p;
362 
363 	for (p = expand_names;  *p;  ++p)
364 		if (memcmp(*p,s,n) == 0  &&  !(*p)[n])
365 			return p - expand_names;
366 	return -1;
367 }
368 
369 
370 	void
ignorephrases(key)371 ignorephrases(key)
372 	const char *key;
373 /*
374 * Ignore a series of phrases that do not start with KEY.
375 * Stop when the next phrase starts with a token that is not an identifier,
376 * or is KEY.
377 */
378 {
379 	for (;;) {
380 		nextlex();
381 		if (nexttok != ID  ||  strcmp(NextString,key) == 0)
382 			break;
383 		warnignore();
384 		hshenter=false;
385 		for (;; nextlex()) {
386 			switch (nexttok) {
387 				case SEMI: hshenter=true; break;
388 				case ID:
389 				case NUM: ffree1(NextString); continue;
390 				case STRING: readstring(); continue;
391 				default: continue;
392 			}
393 			break;
394 		}
395 	}
396 }
397 
398 
399 	static int
getdelta()400 getdelta()
401 /* Function: reads a delta block.
402  * returns false if the current block does not start with a number.
403  */
404 {
405         register struct hshentry * Delta, * num;
406 	struct branchhead **LastBranch, *NewBranch;
407 
408 	if (!(Delta = getdnum()))
409 		return false;
410 
411         hshenter = false; /*Don't enter dates into hashtable*/
412 	Delta->date = getkeyval(Kdate, NUM, false);
413         hshenter=true;    /*reset hshenter for revision numbers.*/
414 
415         Delta->author = getkeyval(Kauthor, ID, false);
416 
417         Delta->state = getkeyval(Kstate, ID, true);
418 
419 	getkey(K_branches);
420 	LastBranch = &Delta->branches;
421 	while ((num = getdnum())) {
422 		NewBranch = ftalloc(struct branchhead);
423                 NewBranch->hsh = num;
424 		*LastBranch = NewBranch;
425 		LastBranch = &NewBranch->nextbranch;
426         }
427 	*LastBranch = 0;
428 	getsemi(K_branches);
429 
430 	getkey(Knext);
431 	Delta->next = num = getdnum();
432 	getsemi(Knext);
433 	Delta->lockedby = 0;
434 	Delta->log.string = 0;
435 	Delta->selector = true;
436 	Delta->ig = getphrases(Kdesc);
437         TotalDeltas++;
438         return (true);
439 }
440 
441 
442 	void
gettree()443 gettree()
444 /* Function: Reads in the delta tree with getdelta(), then
445  * updates the lockedby fields.
446  */
447 {
448 	struct rcslock const *currlock;
449 
450 	while (getdelta())
451 		continue;
452         currlock=Locks;
453         while (currlock) {
454                 currlock->delta->lockedby = currlock->login;
455                 currlock = currlock->nextlock;
456         }
457 }
458 
459 
460 	void
getdesc(prdesc)461 getdesc(prdesc)
462 int  prdesc;
463 /* Function: read in descriptive text
464  * nexttok is not advanced afterwards.
465  * If prdesc is set, the text is printed to stdout.
466  */
467 {
468 
469 	getkeystring(Kdesc);
470         if (prdesc)
471                 printstring();  /*echo string*/
472         else    readstring();   /*skip string*/
473 }
474 
475 
476 
477 
478 
479 
480 	static char const *
getkeyval(keyword,token,optional)481 getkeyval(keyword, token, optional)
482 	char const *keyword;
483 	enum tokens token;
484 	int optional;
485 /* reads a pair of the form
486  * <keyword> <token> ;
487  * where token is one of <id> or <num>. optional indicates whether
488  * <token> is optional. A pointer to
489  * the actual character string of <id> or <num> is returned.
490  */
491 {
492 	register char const *val = 0;
493 
494 	getkey(keyword);
495         if (nexttok==token) {
496                 val = NextString;
497                 nextlex();
498         } else {
499 		if (!optional)
500 			fatserror("missing %s", keyword);
501         }
502 	getsemi(keyword);
503         return(val);
504 }
505 
506 
507 	void
unexpected_EOF()508 unexpected_EOF()
509 {
510 	rcsfaterror("unexpected EOF in diff output");
511 }
512 
513 	void
initdiffcmd(dc)514 initdiffcmd(dc)
515 	register struct diffcmd *dc;
516 /* Initialize *dc suitably for getdiffcmd(). */
517 {
518 	dc->adprev = 0;
519 	dc->dafter = 0;
520 }
521 
522 	static void
badDiffOutput(buf)523 badDiffOutput(buf)
524 	char const *buf;
525 {
526 	rcsfaterror("bad diff output line: %s", buf);
527 }
528 
529 	static void
diffLineNumberTooLarge(buf)530 diffLineNumberTooLarge(buf)
531 	char const *buf;
532 {
533 	rcsfaterror("diff line number too large: %s", buf);
534 }
535 
536 	int
getdiffcmd(finfile,delimiter,foutfile,dc)537 getdiffcmd(finfile, delimiter, foutfile, dc)
538 	RILE *finfile;
539 	FILE *foutfile;
540 	int delimiter;
541 	struct diffcmd *dc;
542 /* Get a editing command output by 'diff -n' from fin.
543  * The input is delimited by SDELIM if delimiter is set, EOF otherwise.
544  * Copy a clean version of the command to fout (if nonnull).
545  * Yield 0 for 'd', 1 for 'a', and -1 for EOF.
546  * Store the command's line number and length into dc->line1 and dc->nlines.
547  * Keep dc->adprev and dc->dafter up to date.
548  */
549 {
550 	register int c;
551 	declarecache;
552 	register FILE *fout;
553 	register char *p;
554 	register RILE *fin;
555 	long line1, nlines, t;
556 	char buf[BUFSIZ];
557 
558 	fin = finfile;
559 	fout = foutfile;
560 	setupcache(fin); cache(fin);
561 	cachegeteof_(c, { if (delimiter) unexpected_EOF(); return -1; } )
562 	if (delimiter) {
563 		if (c==SDELIM) {
564 			cacheget_(c)
565 			if (c==SDELIM) {
566 				buf[0] = c;
567 				buf[1] = 0;
568 				badDiffOutput(buf);
569 			}
570 			uncache(fin);
571 			nextc = c;
572 			if (fout)
573 				aprintf(fout, "%c%c", SDELIM, c);
574 			return -1;
575 		}
576 	}
577 	p = buf;
578 	do {
579 		if (buf+BUFSIZ-2 <= p) {
580 			rcsfaterror("diff output command line too long");
581 		}
582 		*p++ = c;
583 		cachegeteof_(c, unexpected_EOF();)
584 	} while (c != '\n');
585 	uncache(fin);
586 	if (delimiter)
587 		++rcsline;
588 	*p = '\0';
589 	for (p = buf+1;  (c = *p++) == ' ';  )
590 		continue;
591 	line1 = 0;
592 	while (isdigit(c)) {
593 		if (
594 			LONG_MAX/10 < line1  ||
595 			(t = line1 * 10,   (line1 = t + (c - '0'))  <  t)
596 		)
597 			diffLineNumberTooLarge(buf);
598 		c = *p++;
599 	}
600 	while (c == ' ')
601 		c = *p++;
602 	nlines = 0;
603 	while (isdigit(c)) {
604 		if (
605 			LONG_MAX/10 < nlines  ||
606 			(t = nlines * 10,   (nlines = t + (c - '0'))  <  t)
607 		)
608 			diffLineNumberTooLarge(buf);
609 		c = *p++;
610 	}
611 	if (c == '\r')
612 		c = *p++;
613 	if (c || !nlines) {
614 		badDiffOutput(buf);
615 	}
616 	if (line1+nlines < line1)
617 		diffLineNumberTooLarge(buf);
618 	switch (buf[0]) {
619 	    case 'a':
620 		if (line1 < dc->adprev) {
621 		    rcsfaterror("backward insertion in diff output: %s", buf);
622 		}
623 		dc->adprev = line1 + 1;
624 		break;
625 	    case 'd':
626 		if (line1 < dc->adprev  ||  line1 < dc->dafter) {
627 		    rcsfaterror("backward deletion in diff output: %s", buf);
628 		}
629 		dc->adprev = line1;
630 		dc->dafter = line1 + nlines;
631 		break;
632 	    default:
633 		badDiffOutput(buf);
634 	}
635 	if (fout) {
636 		aprintf(fout, "%s\n", buf);
637 	}
638 	dc->line1 = line1;
639 	dc->nlines = nlines;
640 	return buf[0] == 'a';
641 }
642 
643 
644 
645 #ifdef SYNTEST
646 
647 /* Input an RCS file and print its internal data structures.  */
648 
649 char const cmdid[] = "syntest";
650 
651 	int
main(argc,argv)652 main(argc,argv)
653 int argc; char * argv[];
654 {
655 
656         if (argc<2) {
657 		aputs("No input file\n",stderr);
658 		exitmain(EXIT_FAILURE);
659         }
660 	if (!(finptr = Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
661 		faterror("can't open input file %s", argv[1]);
662         }
663         Lexinit();
664         getadmin();
665 	fdlock = STDOUT_FILENO;
666 	putadmin();
667 
668         gettree();
669 
670         getdesc(true);
671 
672 	nextlex();
673 
674 	if (!eoflex()) {
675 		fatserror("expecting EOF");
676         }
677 	exitmain(EXIT_SUCCESS);
678 }
679 
exiterr()680 void exiterr() { _exit(EXIT_FAILURE); }
681 
682 #endif
683