xref: /dragonfly/gnu/usr.bin/rcs/lib/rcssyn.c (revision 86d7f5d305c6adaa56ff4582ece9859d73106103)
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