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 * $FreeBSD: src/gnu/usr.bin/rcs/lib/rcssyn.c,v 1.7 1999/08/27 23:36:48 peter Exp $
40 * $DragonFly: src/gnu/usr.bin/rcs/lib/rcssyn.c,v 1.4 2007/01/21 17:58:42 pavalos Exp $
41 *
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, "$DragonFly: src/gnu/usr.bin/rcs/lib/rcssyn.c,v 1.4 2007/01/21 17:58:42 pavalos 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 Delta->lockedby = 0;
437 Delta->log.string = 0;
438 Delta->selector = true;
439
440 if (getkeyopt(Kcommitid)) {
441 Delta->commitid = NextString;
442 nextlex();
443 getsemi(Kcommitid);
444 } else {
445 Delta->commitid = NULL;
446 }
447
448 Delta->ig = getphrases(Kdesc);
449 TotalDeltas++;
450 return (true);
451 }
452
453
454 void
gettree()455 gettree()
456 /* Function: Reads in the delta tree with getdelta(), then
457 * updates the lockedby fields.
458 */
459 {
460 struct rcslock const *currlock;
461
462 while (getdelta())
463 continue;
464 currlock=Locks;
465 while (currlock) {
466 currlock->delta->lockedby = currlock->login;
467 currlock = currlock->nextlock;
468 }
469 }
470
471
472 void
getdesc(prdesc)473 getdesc(prdesc)
474 int prdesc;
475 /* Function: read in descriptive text
476 * nexttok is not advanced afterwards.
477 * If prdesc is set, the text is printed to stdout.
478 */
479 {
480
481 getkeystring(Kdesc);
482 if (prdesc)
483 printstring(); /*echo string*/
484 else readstring(); /*skip string*/
485 }
486
487
488
489
490
491
492 static char const *
getkeyval(keyword,token,optional)493 getkeyval(keyword, token, optional)
494 char const *keyword;
495 enum tokens token;
496 int optional;
497 /* reads a pair of the form
498 * <keyword> <token> ;
499 * where token is one of <id> or <num>. optional indicates whether
500 * <token> is optional. A pointer to
501 * the actual character string of <id> or <num> is returned.
502 */
503 {
504 register char const *val = 0;
505
506 getkey(keyword);
507 if (nexttok==token) {
508 val = NextString;
509 nextlex();
510 } else {
511 if (!optional)
512 fatserror("missing %s", keyword);
513 }
514 getsemi(keyword);
515 return(val);
516 }
517
518
519 void
unexpected_EOF()520 unexpected_EOF()
521 {
522 rcsfaterror("unexpected EOF in diff output");
523 }
524
525 void
initdiffcmd(dc)526 initdiffcmd(dc)
527 register struct diffcmd *dc;
528 /* Initialize *dc suitably for getdiffcmd(). */
529 {
530 dc->adprev = 0;
531 dc->dafter = 0;
532 }
533
534 static void
badDiffOutput(buf)535 badDiffOutput(buf)
536 char const *buf;
537 {
538 rcsfaterror("bad diff output line: %s", buf);
539 }
540
541 static void
diffLineNumberTooLarge(buf)542 diffLineNumberTooLarge(buf)
543 char const *buf;
544 {
545 rcsfaterror("diff line number too large: %s", buf);
546 }
547
548 int
getdiffcmd(finfile,delimiter,foutfile,dc)549 getdiffcmd(finfile, delimiter, foutfile, dc)
550 RILE *finfile;
551 FILE *foutfile;
552 int delimiter;
553 struct diffcmd *dc;
554 /* Get a editing command output by 'diff -n' from fin.
555 * The input is delimited by SDELIM if delimiter is set, EOF otherwise.
556 * Copy a clean version of the command to fout (if nonnull).
557 * Yield 0 for 'd', 1 for 'a', and -1 for EOF.
558 * Store the command's line number and length into dc->line1 and dc->nlines.
559 * Keep dc->adprev and dc->dafter up to date.
560 */
561 {
562 register int c;
563 declarecache;
564 register FILE *fout;
565 register char *p;
566 register RILE *fin;
567 long line1, nlines, t;
568 char buf[BUFSIZ];
569
570 fin = finfile;
571 fout = foutfile;
572 setupcache(fin); cache(fin);
573 cachegeteof_(c, { if (delimiter) unexpected_EOF(); return -1; } )
574 if (delimiter) {
575 if (c==SDELIM) {
576 cacheget_(c)
577 if (c==SDELIM) {
578 buf[0] = c;
579 buf[1] = 0;
580 badDiffOutput(buf);
581 }
582 uncache(fin);
583 nextc = c;
584 if (fout)
585 aprintf(fout, "%c%c", SDELIM, c);
586 return -1;
587 }
588 }
589 p = buf;
590 do {
591 if (buf+BUFSIZ-2 <= p) {
592 rcsfaterror("diff output command line too long");
593 }
594 *p++ = c;
595 cachegeteof_(c, unexpected_EOF();)
596 } while (c != '\n');
597 uncache(fin);
598 if (delimiter)
599 ++rcsline;
600 *p = '\0';
601 for (p = buf+1; (c = *p++) == ' '; )
602 continue;
603 line1 = 0;
604 while (isdigit(c)) {
605 if (
606 LONG_MAX/10 < line1 ||
607 (t = line1 * 10, (line1 = t + (c - '0')) < t)
608 )
609 diffLineNumberTooLarge(buf);
610 c = *p++;
611 }
612 while (c == ' ')
613 c = *p++;
614 nlines = 0;
615 while (isdigit(c)) {
616 if (
617 LONG_MAX/10 < nlines ||
618 (t = nlines * 10, (nlines = t + (c - '0')) < t)
619 )
620 diffLineNumberTooLarge(buf);
621 c = *p++;
622 }
623 if (c == '\r')
624 c = *p++;
625 if (c || !nlines) {
626 badDiffOutput(buf);
627 }
628 if (line1+nlines < line1)
629 diffLineNumberTooLarge(buf);
630 switch (buf[0]) {
631 case 'a':
632 if (line1 < dc->adprev) {
633 rcsfaterror("backward insertion in diff output: %s", buf);
634 }
635 dc->adprev = line1 + 1;
636 break;
637 case 'd':
638 if (line1 < dc->adprev || line1 < dc->dafter) {
639 rcsfaterror("backward deletion in diff output: %s", buf);
640 }
641 dc->adprev = line1;
642 dc->dafter = line1 + nlines;
643 break;
644 default:
645 badDiffOutput(buf);
646 }
647 if (fout) {
648 aprintf(fout, "%s\n", buf);
649 }
650 dc->line1 = line1;
651 dc->nlines = nlines;
652 return buf[0] == 'a';
653 }
654
655
656
657 #ifdef SYNTEST
658
659 /* Input an RCS file and print its internal data structures. */
660
661 char const cmdid[] = "syntest";
662
663 int
main(argc,argv)664 main(argc,argv)
665 int argc; char * argv[];
666 {
667
668 if (argc<2) {
669 aputs("No input file\n",stderr);
670 exitmain(EXIT_FAILURE);
671 }
672 if (!(finptr = Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
673 faterror("can't open input file %s", argv[1]);
674 }
675 Lexinit();
676 getadmin();
677 fdlock = STDOUT_FILENO;
678 putadmin();
679
680 gettree();
681
682 getdesc(true);
683
684 nextlex();
685
686 if (!eoflex()) {
687 fatserror("expecting EOF");
688 }
689 exitmain(EXIT_SUCCESS);
690 }
691
exiterr()692 void exiterr() { _exit(EXIT_FAILURE); }
693
694 #endif
695