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