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