111894Speter/* RCS file syntactic analysis */
211894Speter
311894Speter/******************************************************************************
49Sjkh *                       Syntax Analysis.
59Sjkh *                       Keyword table
69Sjkh *                       Testprogram: define SYNTEST
79Sjkh *                       Compatibility with Release 2: define COMPAT2=1
811894Speter ******************************************************************************
99Sjkh */
109Sjkh
1111894Speter/* Copyright 1982, 1988, 1989 Walter Tichy
1211894Speter   Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
139Sjkh   Distributed under license by the Free Software Foundation, Inc.
149Sjkh
159SjkhThis file is part of RCS.
169Sjkh
179SjkhRCS is free software; you can redistribute it and/or modify
189Sjkhit under the terms of the GNU General Public License as published by
199Sjkhthe Free Software Foundation; either version 2, or (at your option)
209Sjkhany later version.
219Sjkh
229SjkhRCS is distributed in the hope that it will be useful,
239Sjkhbut WITHOUT ANY WARRANTY; without even the implied warranty of
249SjkhMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
259SjkhGNU General Public License for more details.
269Sjkh
279SjkhYou should have received a copy of the GNU General Public License
2811894Speteralong with RCS; see the file COPYING.
2911894SpeterIf not, write to the Free Software Foundation,
3011894Speter59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
319Sjkh
329SjkhReport problems and direct all questions to:
339Sjkh
349Sjkh    rcs-bugs@cs.purdue.edu
359Sjkh
369Sjkh*/
379Sjkh
3811894Speter/*
3911894Speter * Revision 5.15  1995/06/16 06:19:24  eggert
4011894Speter * Update FSF address.
418858Srgrimes *
4211894Speter * Revision 5.14  1995/06/01 16:23:43  eggert
4311894Speter * (expand_names): Add "b" for -kb.
4411894Speter * (getdelta): Don't strip leading "19" from MKS RCS dates; see cmpdate.
4511894Speter *
4611894Speter * Revision 5.13  1994/03/20 04:52:58  eggert
4711894Speter * Remove lint.
4811894Speter *
4911894Speter * Revision 5.12  1993/11/03 17:42:27  eggert
5011894Speter * Parse MKS RCS dates; ignore \r in diff control lines.
5111894Speter * Don't discard ignored phrases.  Improve quality of diagnostics.
5211894Speter *
5311894Speter * Revision 5.11  1992/07/28  16:12:44  eggert
5411894Speter * Avoid `unsigned'.  Statement macro names now end in _.
5511894Speter *
5611894Speter * Revision 5.10  1992/01/24  18:44:19  eggert
5711894Speter * Move put routines to rcsgen.c.
5811894Speter *
5911894Speter * Revision 5.9  1992/01/06  02:42:34  eggert
6011894Speter * ULONG_MAX/10 -> ULONG_MAX_OVER_10
6111894Speter * while (E) ; -> while (E) continue;
6211894Speter *
639Sjkh * Revision 5.8  1991/08/19  03:13:55  eggert
649Sjkh * Tune.
659Sjkh *
669Sjkh * Revision 5.7  1991/04/21  11:58:29  eggert
679Sjkh * Disambiguate names on shortname hosts.
689Sjkh * Fix errno bug.  Add MS-DOS support.
699Sjkh *
709Sjkh * Revision 5.6  1991/02/28  19:18:51  eggert
719Sjkh * Fix null termination bug in reporting keyword expansion.
729Sjkh *
739Sjkh * Revision 5.5  1991/02/25  07:12:44  eggert
749Sjkh * Check diff output more carefully; avoid overflow.
759Sjkh *
769Sjkh * Revision 5.4  1990/11/01  05:28:48  eggert
779Sjkh * When ignoring unknown phrases, copy them to the output RCS file.
789Sjkh * Permit arbitrary data in logs and comment leaders.
799Sjkh * Don't check for nontext on initial checkin.
809Sjkh *
819Sjkh * Revision 5.3  1990/09/20  07:58:32  eggert
829Sjkh * Remove the test for non-text bytes; it caused more pain than it cured.
839Sjkh *
849Sjkh * Revision 5.2  1990/09/04  08:02:30  eggert
859Sjkh * Parse RCS files with no revisions.
869Sjkh * Don't strip leading white space from diff commands.  Count RCS lines better.
879Sjkh *
889Sjkh * Revision 5.1  1990/08/29  07:14:06  eggert
899Sjkh * Add -kkvl.  Clean old log messages too.
909Sjkh *
919Sjkh * Revision 5.0  1990/08/22  08:13:44  eggert
929Sjkh * Try to parse future RCS formats without barfing.
939Sjkh * Add -k.  Don't require final newline.
949Sjkh * Remove compile-time limits; use malloc instead.
959Sjkh * Don't output branch keyword if there's no default branch,
969Sjkh * because RCS version 3 doesn't understand it.
979Sjkh * Tune.  Remove lint.
989Sjkh * Add support for ISO 8859.  Ansify and Posixate.
999Sjkh * Check that a newly checked-in file is acceptable as input to 'diff'.
1009Sjkh * Check diff's output.
1019Sjkh *
1029Sjkh * Revision 4.6  89/05/01  15:13:32  narten
1039Sjkh * changed copyright header to reflect current distribution rules
1048858Srgrimes *
1059Sjkh * Revision 4.5  88/08/09  19:13:21  eggert
1069Sjkh * Allow cc -R; remove lint.
1078858Srgrimes *
1089Sjkh * Revision 4.4  87/12/18  11:46:16  narten
1099Sjkh * more lint cleanups (Guy Harris)
1108858Srgrimes *
1119Sjkh * Revision 4.3  87/10/18  10:39:36  narten
1129Sjkh * Updating version numbers. Changes relative to 1.1 actually relative to
1139Sjkh * 4.1
1148858Srgrimes *
1159Sjkh * Revision 1.3  87/09/24  14:00:49  narten
1168858Srgrimes * Sources now pass through lint (if you ignore printf/sprintf/fprintf
1179Sjkh * warnings)
1188858Srgrimes *
1199Sjkh * Revision 1.2  87/03/27  14:22:40  jenkins
1209Sjkh * Port to suns
1218858Srgrimes *
1229Sjkh * Revision 4.1  83/03/28  11:38:49  wft
1239Sjkh * Added parsing and printing of default branch.
1248858Srgrimes *
1259Sjkh * Revision 3.6  83/01/15  17:46:50  wft
1269Sjkh * Changed readdelta() to initialize selector and log-pointer.
1279Sjkh * Changed puttree to check for selector==DELETE; putdtext() uses DELNUMFORM.
1289Sjkh *
1299Sjkh * Revision 3.5  82/12/08  21:58:58  wft
1309Sjkh * renamed Commentleader to Commleader.
1319Sjkh *
1329Sjkh * Revision 3.4  82/12/04  13:24:40  wft
1339Sjkh * Added routine gettree(), which updates keeplock after reading the
1349Sjkh * delta tree.
1359Sjkh *
1369Sjkh * Revision 3.3  82/11/28  21:30:11  wft
1379Sjkh * Reading and printing of Suffix removed; version COMPAT2 skips the
1389Sjkh * Suffix for files of release 2 format. Fixed problems with printing nil.
1399Sjkh *
1409Sjkh * Revision 3.2  82/10/18  21:18:25  wft
1419Sjkh * renamed putdeltatext to putdtext.
1429Sjkh *
1439Sjkh * Revision 3.1  82/10/11  19:45:11  wft
1449Sjkh * made sure getc() returns into an integer.
1459Sjkh */
1469Sjkh
1479Sjkh
1489Sjkh
1499Sjkh/* version COMPAT2 reads files of the format of release 2 and 3, but
1509Sjkh * generates files of release 3 format. Need not be defined if no
1519Sjkh * old RCS files generated with release 2 exist.
1529Sjkh */
1539Sjkh
1549Sjkh#include "rcsbase.h"
1559Sjkh
15650472SpeterlibId(synId, "$FreeBSD$")
1579Sjkh
1589Sjkhstatic char const *getkeyval P((char const*,enum tokens,int));
15911894Speterstatic int getdelta P((void));
1609Sjkhstatic int strn2expmode P((char const*,size_t));
16111894Speterstatic struct hshentry *getdnum P((void));
16211894Speterstatic void badDiffOutput P((char const*)) exiting;
16311894Speterstatic void diffLineNumberTooLarge P((char const*)) exiting;
16411894Speterstatic void getsemi P((char const*));
1659Sjkh
1669Sjkh/* keyword table */
1679Sjkh
1689Sjkhchar const
1699Sjkh	Kaccess[]   = "access",
1709Sjkh	Kauthor[]   = "author",
1719Sjkh	Kbranch[]   = "branch",
1729Sjkh	Kcomment[]  = "comment",
1739Sjkh	Kdate[]     = "date",
17411894Speter	Kdesc[]     = "desc",
1759Sjkh	Kexpand[]   = "expand",
1769Sjkh	Khead[]     = "head",
1779Sjkh	Klocks[]    = "locks",
17811894Speter	Klog[]      = "log",
1799Sjkh	Knext[]     = "next",
1809Sjkh	Kstate[]    = "state",
1819Sjkh	Kstrict[]   = "strict",
18211894Speter	Ksymbols[]  = "symbols",
18311894Speter	Ktext[]     = "text";
18411894Speter
18511894Speterstatic char const
1869Sjkh#if COMPAT2
1879Sjkh	Ksuffix[]   = "suffix",
1889Sjkh#endif
18911894Speter	K_branches[]= "branches";
1909Sjkh
1919Sjkhstatic struct buf Commleader;
1929Sjkhstruct cbuf Comment;
19311894Speterstruct cbuf Ignored;
1949Sjkhstruct access   * AccessList;
1959Sjkhstruct assoc    * Symbols;
19611894Speterstruct rcslock *Locks;
1979Sjkhint		  Expand;
1989Sjkhint               StrictLocks;
1999Sjkhstruct hshentry * Head;
2009Sjkhchar const      * Dbranch;
20111894Speterint TotalDeltas;
2029Sjkh
2039Sjkh
2049Sjkh	static void
2059Sjkhgetsemi(key)
2069Sjkh	char const *key;
2079Sjkh/* Get a semicolon to finish off a phrase started by KEY.  */
2089Sjkh{
2099Sjkh	if (!getlex(SEMI))
2109Sjkh		fatserror("missing ';' after '%s'", key);
2119Sjkh}
2129Sjkh
2139Sjkh	static struct hshentry *
2149Sjkhgetdnum()
2159Sjkh/* Get a delta number.  */
2169Sjkh{
2179Sjkh	register struct hshentry *delta = getnum();
2189Sjkh	if (delta && countnumflds(delta->num)&1)
2199Sjkh		fatserror("%s isn't a delta number", delta->num);
2209Sjkh	return delta;
2219Sjkh}
2229Sjkh
2239Sjkh
2249Sjkh	void
2259Sjkhgetadmin()
2269Sjkh/* Read an <admin> and initialize the appropriate global variables.  */
2279Sjkh{
2289Sjkh	register char const *id;
2299Sjkh        struct access   * newaccess;
2309Sjkh        struct assoc    * newassoc;
23111894Speter	struct rcslock *newlock;
2329Sjkh        struct hshentry * delta;
2339Sjkh	struct access **LastAccess;
2349Sjkh	struct assoc **LastSymbol;
23511894Speter	struct rcslock **LastLock;
2369Sjkh	struct buf b;
2379Sjkh	struct cbuf cb;
2389Sjkh
2399Sjkh        TotalDeltas=0;
2409Sjkh
2419Sjkh	getkey(Khead);
2429Sjkh	Head = getdnum();
2439Sjkh	getsemi(Khead);
2449Sjkh
24511894Speter	Dbranch = 0;
2469Sjkh	if (getkeyopt(Kbranch)) {
2479Sjkh		if ((delta = getnum()))
2489Sjkh			Dbranch = delta->num;
2499Sjkh		getsemi(Kbranch);
2509Sjkh        }
2519Sjkh
2529Sjkh
2539Sjkh#if COMPAT2
2549Sjkh        /* read suffix. Only in release 2 format */
2559Sjkh	if (getkeyopt(Ksuffix)) {
2569Sjkh                if (nexttok==STRING) {
2579Sjkh			readstring(); nextlex(); /* Throw away the suffix.  */
2589Sjkh		} else if (nexttok==ID) {
2599Sjkh                        nextlex();
2609Sjkh                }
2619Sjkh		getsemi(Ksuffix);
2629Sjkh        }
2639Sjkh#endif
2649Sjkh
2659Sjkh	getkey(Kaccess);
2669Sjkh	LastAccess = &AccessList;
26711894Speter	while ((id = getid())) {
2689Sjkh		newaccess = ftalloc(struct access);
2699Sjkh                newaccess->login = id;
2709Sjkh		*LastAccess = newaccess;
2719Sjkh		LastAccess = &newaccess->nextaccess;
2729Sjkh        }
27311894Speter	*LastAccess = 0;
2749Sjkh	getsemi(Kaccess);
2759Sjkh
2769Sjkh	getkey(Ksymbols);
2779Sjkh	LastSymbol = &Symbols;
27811894Speter        while ((id = getid())) {
2799Sjkh                if (!getlex(COLON))
2809Sjkh			fatserror("missing ':' in symbolic name definition");
2819Sjkh                if (!(delta=getnum())) {
2829Sjkh			fatserror("missing number in symbolic name definition");
2839Sjkh                } else { /*add new pair to association list*/
2849Sjkh			newassoc = ftalloc(struct assoc);
2859Sjkh                        newassoc->symbol=id;
2869Sjkh			newassoc->num = delta->num;
2879Sjkh			*LastSymbol = newassoc;
2889Sjkh			LastSymbol = &newassoc->nextassoc;
2899Sjkh                }
2909Sjkh        }
29111894Speter	*LastSymbol = 0;
2929Sjkh	getsemi(Ksymbols);
2939Sjkh
2949Sjkh	getkey(Klocks);
2959Sjkh	LastLock = &Locks;
29611894Speter        while ((id = getid())) {
2979Sjkh                if (!getlex(COLON))
2989Sjkh			fatserror("missing ':' in lock");
2999Sjkh		if (!(delta=getdnum())) {
3009Sjkh			fatserror("missing number in lock");
3019Sjkh                } else { /*add new pair to lock list*/
30211894Speter			newlock = ftalloc(struct rcslock);
3039Sjkh                        newlock->login=id;
3049Sjkh                        newlock->delta=delta;
3059Sjkh			*LastLock = newlock;
3069Sjkh			LastLock = &newlock->nextlock;
3079Sjkh                }
3089Sjkh        }
30911894Speter	*LastLock = 0;
3109Sjkh	getsemi(Klocks);
3119Sjkh
3129Sjkh	if ((StrictLocks = getkeyopt(Kstrict)))
3139Sjkh		getsemi(Kstrict);
3149Sjkh
31511894Speter	clear_buf(&Comment);
3169Sjkh	if (getkeyopt(Kcomment)) {
3179Sjkh		if (nexttok==STRING) {
3189Sjkh			Comment = savestring(&Commleader);
3199Sjkh			nextlex();
3209Sjkh		}
3219Sjkh		getsemi(Kcomment);
3229Sjkh        }
3239Sjkh
3249Sjkh	Expand = KEYVAL_EXPAND;
3259Sjkh	if (getkeyopt(Kexpand)) {
3269Sjkh		if (nexttok==STRING) {
3279Sjkh			bufautobegin(&b);
3289Sjkh			cb = savestring(&b);
3299Sjkh			if ((Expand = strn2expmode(cb.string,cb.size)) < 0)
3309Sjkh			    fatserror("unknown expand mode %.*s",
3319Sjkh				(int)cb.size, cb.string
3329Sjkh			    );
3339Sjkh			bufautoend(&b);
3349Sjkh			nextlex();
3359Sjkh		}
3369Sjkh		getsemi(Kexpand);
3379Sjkh        }
3389Sjkh	Ignored = getphrases(Kdesc);
3399Sjkh}
3409Sjkh
3419Sjkhchar const *const expand_names[] = {
3429Sjkh	/* These must agree with *_EXPAND in rcsbase.h.  */
34311894Speter	"kv", "kvl", "k", "v", "o", "b",
3449Sjkh	0
3459Sjkh};
3469Sjkh
3479Sjkh	int
3489Sjkhstr2expmode(s)
3499Sjkh	char const *s;
3509Sjkh/* Yield expand mode corresponding to S, or -1 if bad.  */
3519Sjkh{
3529Sjkh	return strn2expmode(s, strlen(s));
3539Sjkh}
3549Sjkh
3559Sjkh	static int
3569Sjkhstrn2expmode(s, n)
3579Sjkh	char const *s;
3589Sjkh	size_t n;
3599Sjkh{
3609Sjkh	char const *const *p;
3619Sjkh
3629Sjkh	for (p = expand_names;  *p;  ++p)
3639Sjkh		if (memcmp(*p,s,n) == 0  &&  !(*p)[n])
3649Sjkh			return p - expand_names;
3659Sjkh	return -1;
3669Sjkh}
3679Sjkh
3689Sjkh
3699Sjkh	void
37011894Speterignorephrases(key)
37111894Speter	const char *key;
37211894Speter/*
37311894Speter* Ignore a series of phrases that do not start with KEY.
37411894Speter* Stop when the next phrase starts with a token that is not an identifier,
37511894Speter* or is KEY.
37611894Speter*/
3779Sjkh{
3789Sjkh	for (;;) {
37911894Speter		nextlex();
38011894Speter		if (nexttok != ID  ||  strcmp(NextString,key) == 0)
38111894Speter			break;
38211894Speter		warnignore();
38311894Speter		hshenter=false;
38411894Speter		for (;; nextlex()) {
38511894Speter			switch (nexttok) {
38611894Speter				case SEMI: hshenter=true; break;
38711894Speter				case ID:
38811894Speter				case NUM: ffree1(NextString); continue;
38911894Speter				case STRING: readstring(); continue;
39011894Speter				default: continue;
39111894Speter			}
39211894Speter			break;
39311894Speter		}
3949Sjkh	}
3959Sjkh}
3969Sjkh
3979Sjkh
3989Sjkh	static int
3999Sjkhgetdelta()
4009Sjkh/* Function: reads a delta block.
4019Sjkh * returns false if the current block does not start with a number.
4029Sjkh */
4039Sjkh{
4049Sjkh        register struct hshentry * Delta, * num;
4059Sjkh	struct branchhead **LastBranch, *NewBranch;
4069Sjkh
4079Sjkh	if (!(Delta = getdnum()))
4089Sjkh		return false;
4099Sjkh
4109Sjkh        hshenter = false; /*Don't enter dates into hashtable*/
41111894Speter	Delta->date = getkeyval(Kdate, NUM, false);
4129Sjkh        hshenter=true;    /*reset hshenter for revision numbers.*/
4139Sjkh
4149Sjkh        Delta->author = getkeyval(Kauthor, ID, false);
4159Sjkh
4169Sjkh        Delta->state = getkeyval(Kstate, ID, true);
4179Sjkh
4189Sjkh	getkey(K_branches);
4199Sjkh	LastBranch = &Delta->branches;
4209Sjkh	while ((num = getdnum())) {
4219Sjkh		NewBranch = ftalloc(struct branchhead);
4229Sjkh                NewBranch->hsh = num;
4239Sjkh		*LastBranch = NewBranch;
4249Sjkh		LastBranch = &NewBranch->nextbranch;
4259Sjkh        }
42611894Speter	*LastBranch = 0;
4279Sjkh	getsemi(K_branches);
4289Sjkh
4299Sjkh	getkey(Knext);
4309Sjkh	Delta->next = num = getdnum();
4319Sjkh	getsemi(Knext);
43211894Speter	Delta->lockedby = 0;
4339Sjkh	Delta->log.string = 0;
4349Sjkh	Delta->selector = true;
4359Sjkh	Delta->ig = getphrases(Kdesc);
4369Sjkh        TotalDeltas++;
4379Sjkh        return (true);
4389Sjkh}
4399Sjkh
4409Sjkh
4419Sjkh	void
4429Sjkhgettree()
4439Sjkh/* Function: Reads in the delta tree with getdelta(), then
4449Sjkh * updates the lockedby fields.
4459Sjkh */
4469Sjkh{
44711894Speter	struct rcslock const *currlock;
4489Sjkh
44911894Speter	while (getdelta())
45011894Speter		continue;
4519Sjkh        currlock=Locks;
4529Sjkh        while (currlock) {
4539Sjkh                currlock->delta->lockedby = currlock->login;
4549Sjkh                currlock = currlock->nextlock;
4559Sjkh        }
4569Sjkh}
4579Sjkh
4589Sjkh
4599Sjkh	void
4609Sjkhgetdesc(prdesc)
4619Sjkhint  prdesc;
4629Sjkh/* Function: read in descriptive text
4639Sjkh * nexttok is not advanced afterwards.
4649Sjkh * If prdesc is set, the text is printed to stdout.
4659Sjkh */
4669Sjkh{
4679Sjkh
4689Sjkh	getkeystring(Kdesc);
4699Sjkh        if (prdesc)
4709Sjkh                printstring();  /*echo string*/
4719Sjkh        else    readstring();   /*skip string*/
4729Sjkh}
4739Sjkh
4749Sjkh
4759Sjkh
4769Sjkh
4779Sjkh
4789Sjkh
4799Sjkh	static char const *
4809Sjkhgetkeyval(keyword, token, optional)
4819Sjkh	char const *keyword;
4829Sjkh	enum tokens token;
4839Sjkh	int optional;
4849Sjkh/* reads a pair of the form
4859Sjkh * <keyword> <token> ;
4869Sjkh * where token is one of <id> or <num>. optional indicates whether
4879Sjkh * <token> is optional. A pointer to
4889Sjkh * the actual character string of <id> or <num> is returned.
4899Sjkh */
4909Sjkh{
49111894Speter	register char const *val = 0;
4929Sjkh
4939Sjkh	getkey(keyword);
4949Sjkh        if (nexttok==token) {
4959Sjkh                val = NextString;
4969Sjkh                nextlex();
4979Sjkh        } else {
4989Sjkh		if (!optional)
4999Sjkh			fatserror("missing %s", keyword);
5009Sjkh        }
5019Sjkh	getsemi(keyword);
5029Sjkh        return(val);
5039Sjkh}
5049Sjkh
5059Sjkh
5069Sjkh	void
5079Sjkhunexpected_EOF()
5089Sjkh{
50911894Speter	rcsfaterror("unexpected EOF in diff output");
5109Sjkh}
5119Sjkh
5129Sjkh	void
5139Sjkhinitdiffcmd(dc)
5149Sjkh	register struct diffcmd *dc;
5159Sjkh/* Initialize *dc suitably for getdiffcmd(). */
5169Sjkh{
5179Sjkh	dc->adprev = 0;
5189Sjkh	dc->dafter = 0;
5199Sjkh}
5209Sjkh
52111894Speter	static void
5229SjkhbadDiffOutput(buf)
5239Sjkh	char const *buf;
5249Sjkh{
52511894Speter	rcsfaterror("bad diff output line: %s", buf);
5269Sjkh}
5279Sjkh
52811894Speter	static void
5299SjkhdiffLineNumberTooLarge(buf)
5309Sjkh	char const *buf;
5319Sjkh{
53211894Speter	rcsfaterror("diff line number too large: %s", buf);
5339Sjkh}
5349Sjkh
5359Sjkh	int
5369Sjkhgetdiffcmd(finfile, delimiter, foutfile, dc)
5379Sjkh	RILE *finfile;
5389Sjkh	FILE *foutfile;
5399Sjkh	int delimiter;
5409Sjkh	struct diffcmd *dc;
5419Sjkh/* Get a editing command output by 'diff -n' from fin.
5429Sjkh * The input is delimited by SDELIM if delimiter is set, EOF otherwise.
5439Sjkh * Copy a clean version of the command to fout (if nonnull).
5449Sjkh * Yield 0 for 'd', 1 for 'a', and -1 for EOF.
5459Sjkh * Store the command's line number and length into dc->line1 and dc->nlines.
5469Sjkh * Keep dc->adprev and dc->dafter up to date.
5479Sjkh */
5489Sjkh{
5499Sjkh	register int c;
5509Sjkh	declarecache;
5519Sjkh	register FILE *fout;
5529Sjkh	register char *p;
5539Sjkh	register RILE *fin;
55411894Speter	long line1, nlines, t;
5559Sjkh	char buf[BUFSIZ];
5569Sjkh
5579Sjkh	fin = finfile;
5589Sjkh	fout = foutfile;
5599Sjkh	setupcache(fin); cache(fin);
56011894Speter	cachegeteof_(c, { if (delimiter) unexpected_EOF(); return -1; } )
5619Sjkh	if (delimiter) {
5629Sjkh		if (c==SDELIM) {
56311894Speter			cacheget_(c)
5649Sjkh			if (c==SDELIM) {
5659Sjkh				buf[0] = c;
5669Sjkh				buf[1] = 0;
5679Sjkh				badDiffOutput(buf);
5689Sjkh			}
5699Sjkh			uncache(fin);
5709Sjkh			nextc = c;
5719Sjkh			if (fout)
5729Sjkh				aprintf(fout, "%c%c", SDELIM, c);
5739Sjkh			return -1;
5749Sjkh		}
5759Sjkh	}
5769Sjkh	p = buf;
5779Sjkh	do {
5789Sjkh		if (buf+BUFSIZ-2 <= p) {
57911894Speter			rcsfaterror("diff output command line too long");
5809Sjkh		}
5819Sjkh		*p++ = c;
58211894Speter		cachegeteof_(c, unexpected_EOF();)
5839Sjkh	} while (c != '\n');
5849Sjkh	uncache(fin);
5859Sjkh	if (delimiter)
5869Sjkh		++rcsline;
5879Sjkh	*p = '\0';
5889Sjkh	for (p = buf+1;  (c = *p++) == ' ';  )
58911894Speter		continue;
5909Sjkh	line1 = 0;
5919Sjkh	while (isdigit(c)) {
5929Sjkh		if (
59311894Speter			LONG_MAX/10 < line1  ||
59411894Speter			(t = line1 * 10,   (line1 = t + (c - '0'))  <  t)
5959Sjkh		)
5969Sjkh			diffLineNumberTooLarge(buf);
5979Sjkh		c = *p++;
5989Sjkh	}
5999Sjkh	while (c == ' ')
6009Sjkh		c = *p++;
6019Sjkh	nlines = 0;
6029Sjkh	while (isdigit(c)) {
6039Sjkh		if (
60411894Speter			LONG_MAX/10 < nlines  ||
60511894Speter			(t = nlines * 10,   (nlines = t + (c - '0'))  <  t)
6069Sjkh		)
6079Sjkh			diffLineNumberTooLarge(buf);
6089Sjkh		c = *p++;
6099Sjkh	}
61011894Speter	if (c == '\r')
61111894Speter		c = *p++;
6129Sjkh	if (c || !nlines) {
6139Sjkh		badDiffOutput(buf);
6149Sjkh	}
6159Sjkh	if (line1+nlines < line1)
6169Sjkh		diffLineNumberTooLarge(buf);
6179Sjkh	switch (buf[0]) {
6189Sjkh	    case 'a':
6199Sjkh		if (line1 < dc->adprev) {
62011894Speter		    rcsfaterror("backward insertion in diff output: %s", buf);
6219Sjkh		}
6229Sjkh		dc->adprev = line1 + 1;
6239Sjkh		break;
6249Sjkh	    case 'd':
6259Sjkh		if (line1 < dc->adprev  ||  line1 < dc->dafter) {
62611894Speter		    rcsfaterror("backward deletion in diff output: %s", buf);
6279Sjkh		}
6289Sjkh		dc->adprev = line1;
6299Sjkh		dc->dafter = line1 + nlines;
6309Sjkh		break;
6319Sjkh	    default:
6329Sjkh		badDiffOutput(buf);
6339Sjkh	}
6349Sjkh	if (fout) {
6359Sjkh		aprintf(fout, "%s\n", buf);
6369Sjkh	}
6379Sjkh	dc->line1 = line1;
6389Sjkh	dc->nlines = nlines;
6399Sjkh	return buf[0] == 'a';
6409Sjkh}
6419Sjkh
6429Sjkh
6439Sjkh
6449Sjkh#ifdef SYNTEST
6459Sjkh
64611894Speter/* Input an RCS file and print its internal data structures.  */
64711894Speter
6489Sjkhchar const cmdid[] = "syntest";
6499Sjkh
6509Sjkh	int
6519Sjkhmain(argc,argv)
6529Sjkhint argc; char * argv[];
6539Sjkh{
6549Sjkh
6559Sjkh        if (argc<2) {
6569Sjkh		aputs("No input file\n",stderr);
6579Sjkh		exitmain(EXIT_FAILURE);
6589Sjkh        }
6599Sjkh	if (!(finptr = Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
6609Sjkh		faterror("can't open input file %s", argv[1]);
6619Sjkh        }
6629Sjkh        Lexinit();
6639Sjkh        getadmin();
66411894Speter	fdlock = STDOUT_FILENO;
66511894Speter	putadmin();
6669Sjkh
6679Sjkh        gettree();
6689Sjkh
6699Sjkh        getdesc(true);
6709Sjkh
6719Sjkh	nextlex();
6729Sjkh
6739Sjkh	if (!eoflex()) {
6749Sjkh		fatserror("expecting EOF");
6759Sjkh        }
6769Sjkh	exitmain(EXIT_SUCCESS);
6779Sjkh}
6789Sjkh
67911894Spetervoid exiterr() { _exit(EXIT_FAILURE); }
6809Sjkh
6819Sjkh#endif
682