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