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
17 This file is part of RCS.
18
19 RCS is free software; you can redistribute it and/or modify
20 it under the terms of the GNU General Public License as published by
21 the Free Software Foundation; either version 2, or (at your option)
22 any later version.
23
24 RCS is distributed in the hope that it will be useful,
25 but WITHOUT ANY WARRANTY; without even the implied warranty of
26 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 GNU General Public License for more details.
28
29 You should have received a copy of the GNU General Public License
30 along with RCS; see the file COPYING.
31 If not, write to the Free Software Foundation,
32 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
33
34 Report 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
159 libId(synId, "Id: rcssyn.c,v 5.15 1995/06/16 06:19:24 eggert Exp ")
160
161 static char const *getkeyval P((char const*,enum tokens,int));
162 static int getdelta P((void));
163 static int strn2expmode P((char const*,size_t));
164 static struct hshentry *getdnum P((void));
165 static void badDiffOutput P((char const*)) exiting;
166 static void diffLineNumberTooLarge P((char const*)) exiting;
167 static void getsemi P((char const*));
168
169 /* keyword table */
170
171 char 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
189 static char const
190 #if COMPAT2
191 Ksuffix[] = "suffix",
192 #endif
193 K_branches[]= "branches";
194
195 static struct buf Commleader;
196 struct cbuf Comment;
197 struct cbuf Ignored;
198 struct access * AccessList;
199 struct assoc * Symbols;
200 struct rcslock *Locks;
201 int Expand;
202 int StrictLocks;
203 struct hshentry * Head;
204 char const * Dbranch;
205 int TotalDeltas;
206
207
208 static void
getsemi(key)209 getsemi(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 *
getdnum()218 getdnum()
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
getadmin()229 getadmin()
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
345 char 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
str2expmode(s)352 str2expmode(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
strn2expmode(s,n)360 strn2expmode(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
ignorephrases(key)374 ignorephrases(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
getdelta()403 getdelta()
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
gettree()457 gettree()
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
getdesc(prdesc)475 getdesc(prdesc)
476 int 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 *
getkeyval(keyword,token,optional)495 getkeyval(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
unexpected_EOF()522 unexpected_EOF()
523 {
524 rcsfaterror("unexpected EOF in diff output");
525 }
526
527 void
initdiffcmd(dc)528 initdiffcmd(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
badDiffOutput(buf)537 badDiffOutput(buf)
538 char const *buf;
539 {
540 rcsfaterror("bad diff output line: %s", buf);
541 }
542
543 static void
diffLineNumberTooLarge(buf)544 diffLineNumberTooLarge(buf)
545 char const *buf;
546 {
547 rcsfaterror("diff line number too large: %s", buf);
548 }
549
550 int
getdiffcmd(finfile,delimiter,foutfile,dc)551 getdiffcmd(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
663 char const cmdid[] = "syntest";
664
665 int
main(argc,argv)666 main(argc,argv)
667 int 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
exiterr()694 void exiterr() { _exit(EXIT_FAILURE); }
695
696 #endif
697