rcssyn.c revision 8858
1/*
2 *                     RCS file input
3 */
4/*********************************************************************************
5 *                       Syntax Analysis.
6 *                       Keyword table
7 *                       Testprogram: define SYNTEST
8 *                       Compatibility with Release 2: define COMPAT2=1
9 *********************************************************************************
10 */
11
12/* Copyright (C) 1982, 1988, 1989 Walter Tichy
13   Copyright 1990, 1991 by Paul Eggert
14   Distributed under license by the Free Software Foundation, Inc.
15
16This file is part of RCS.
17
18RCS is free software; you can redistribute it and/or modify
19it under the terms of the GNU General Public License as published by
20the Free Software Foundation; either version 2, or (at your option)
21any later version.
22
23RCS is distributed in the hope that it will be useful,
24but WITHOUT ANY WARRANTY; without even the implied warranty of
25MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26GNU General Public License for more details.
27
28You should have received a copy of the GNU General Public License
29along with RCS; see the file COPYING.  If not, write to
30the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
31
32Report problems and direct all questions to:
33
34    rcs-bugs@cs.purdue.edu
35
36*/
37
38
39/* $Log: rcssyn.c,v $
40 * Revision 1.1.1.1  1993/06/18  04:22:12  jkh
41 * Updated GNU utilities
42 *
43 * Revision 5.8  1991/08/19  03:13:55  eggert
44 * Tune.
45 *
46 * Revision 5.7  1991/04/21  11:58:29  eggert
47 * Disambiguate names on shortname hosts.
48 * Fix errno bug.  Add MS-DOS support.
49 *
50 * Revision 5.6  1991/02/28  19:18:51  eggert
51 * Fix null termination bug in reporting keyword expansion.
52 *
53 * Revision 5.5  1991/02/25  07:12:44  eggert
54 * Check diff output more carefully; avoid overflow.
55 *
56 * Revision 5.4  1990/11/01  05:28:48  eggert
57 * When ignoring unknown phrases, copy them to the output RCS file.
58 * Permit arbitrary data in logs and comment leaders.
59 * Don't check for nontext on initial checkin.
60 *
61 * Revision 5.3  1990/09/20  07:58:32  eggert
62 * Remove the test for non-text bytes; it caused more pain than it cured.
63 *
64 * Revision 5.2  1990/09/04  08:02:30  eggert
65 * Parse RCS files with no revisions.
66 * Don't strip leading white space from diff commands.  Count RCS lines better.
67 *
68 * Revision 5.1  1990/08/29  07:14:06  eggert
69 * Add -kkvl.  Clean old log messages too.
70 *
71 * Revision 5.0  1990/08/22  08:13:44  eggert
72 * Try to parse future RCS formats without barfing.
73 * Add -k.  Don't require final newline.
74 * Remove compile-time limits; use malloc instead.
75 * Don't output branch keyword if there's no default branch,
76 * because RCS version 3 doesn't understand it.
77 * Tune.  Remove lint.
78 * Add support for ISO 8859.  Ansify and Posixate.
79 * Check that a newly checked-in file is acceptable as input to 'diff'.
80 * Check diff's output.
81 *
82 * Revision 4.6  89/05/01  15:13:32  narten
83 * changed copyright header to reflect current distribution rules
84 *
85 * Revision 4.5  88/08/09  19:13:21  eggert
86 * Allow cc -R; remove lint.
87 *
88 * Revision 4.4  87/12/18  11:46:16  narten
89 * more lint cleanups (Guy Harris)
90 *
91 * Revision 4.3  87/10/18  10:39:36  narten
92 * Updating version numbers. Changes relative to 1.1 actually relative to
93 * 4.1
94 *
95 * Revision 1.3  87/09/24  14:00:49  narten
96 * Sources now pass through lint (if you ignore printf/sprintf/fprintf
97 * warnings)
98 *
99 * Revision 1.2  87/03/27  14:22:40  jenkins
100 * Port to suns
101 *
102 * Revision 4.1  83/03/28  11:38:49  wft
103 * Added parsing and printing of default branch.
104 *
105 * Revision 3.6  83/01/15  17:46:50  wft
106 * Changed readdelta() to initialize selector and log-pointer.
107 * Changed puttree to check for selector==DELETE; putdtext() uses DELNUMFORM.
108 *
109 * Revision 3.5  82/12/08  21:58:58  wft
110 * renamed Commentleader to Commleader.
111 *
112 * Revision 3.4  82/12/04  13:24:40  wft
113 * Added routine gettree(), which updates keeplock after reading the
114 * delta tree.
115 *
116 * Revision 3.3  82/11/28  21:30:11  wft
117 * Reading and printing of Suffix removed; version COMPAT2 skips the
118 * Suffix for files of release 2 format. Fixed problems with printing nil.
119 *
120 * Revision 3.2  82/10/18  21:18:25  wft
121 * renamed putdeltatext to putdtext.
122 *
123 * Revision 3.1  82/10/11  19:45:11  wft
124 * made sure getc() returns into an integer.
125 */
126
127
128
129/* version COMPAT2 reads files of the format of release 2 and 3, but
130 * generates files of release 3 format. Need not be defined if no
131 * old RCS files generated with release 2 exist.
132 */
133/* version SYNTEST inputs a RCS file and then prints out its internal
134 * data structures.
135*/
136
137#include "rcsbase.h"
138
139libId(synId, "$Id: rcssyn.c,v 1.1.1.1 1993/06/18 04:22:12 jkh Exp $")
140
141/* forward */
142static char const *getkeyval P((char const*,enum tokens,int));
143static int strn2expmode P((char const*,size_t));
144
145/* keyword table */
146
147char const
148	Kdesc[]     = "desc",
149	Klog[]      = "log",
150	Ktext[]     = "text";
151
152static char const
153	Kaccess[]   = "access",
154	Kauthor[]   = "author",
155	Kbranch[]   = "branch",
156	K_branches[]= "branches",
157	Kcomment[]  = "comment",
158	Kdate[]     = "date",
159	Kexpand[]   = "expand",
160	Khead[]     = "head",
161	Klocks[]    = "locks",
162	Knext[]     = "next",
163	Kstate[]    = "state",
164	Kstrict[]   = "strict",
165#if COMPAT2
166	Ksuffix[]   = "suffix",
167#endif
168	Ksymbols[]  = "symbols";
169
170static struct buf Commleader;
171static struct cbuf Ignored;
172struct cbuf Comment;
173struct access   * AccessList;
174struct assoc    * Symbols;
175struct lock     * Locks;
176int		  Expand;
177int               StrictLocks;
178struct hshentry * Head;
179char const      * Dbranch;
180unsigned TotalDeltas;
181
182
183	static void
184getsemi(key)
185	char const *key;
186/* Get a semicolon to finish off a phrase started by KEY.  */
187{
188	if (!getlex(SEMI))
189		fatserror("missing ';' after '%s'", key);
190}
191
192	static struct hshentry *
193getdnum()
194/* Get a delta number.  */
195{
196	register struct hshentry *delta = getnum();
197	if (delta && countnumflds(delta->num)&1)
198		fatserror("%s isn't a delta number", delta->num);
199	return delta;
200}
201
202
203	void
204getadmin()
205/* Read an <admin> and initialize the appropriate global variables.  */
206{
207	register char const *id;
208        struct access   * newaccess;
209        struct assoc    * newassoc;
210        struct lock     * newlock;
211        struct hshentry * delta;
212	struct access **LastAccess;
213	struct assoc **LastSymbol;
214	struct lock **LastLock;
215	struct buf b;
216	struct cbuf cb;
217
218        TotalDeltas=0;
219
220	getkey(Khead);
221	Head = getdnum();
222	getsemi(Khead);
223
224	Dbranch = nil;
225	if (getkeyopt(Kbranch)) {
226		if ((delta = getnum()))
227			Dbranch = delta->num;
228		getsemi(Kbranch);
229        }
230
231
232#if COMPAT2
233        /* read suffix. Only in release 2 format */
234	if (getkeyopt(Ksuffix)) {
235                if (nexttok==STRING) {
236			readstring(); nextlex(); /* Throw away the suffix.  */
237		} else if (nexttok==ID) {
238                        nextlex();
239                }
240		getsemi(Ksuffix);
241        }
242#endif
243
244	getkey(Kaccess);
245	LastAccess = &AccessList;
246        while (id=getid()) {
247		newaccess = ftalloc(struct access);
248                newaccess->login = id;
249		*LastAccess = newaccess;
250		LastAccess = &newaccess->nextaccess;
251        }
252	*LastAccess = nil;
253	getsemi(Kaccess);
254
255	getkey(Ksymbols);
256	LastSymbol = &Symbols;
257        while (id = getid()) {
258                if (!getlex(COLON))
259			fatserror("missing ':' in symbolic name definition");
260                if (!(delta=getnum())) {
261			fatserror("missing number in symbolic name definition");
262                } else { /*add new pair to association list*/
263			newassoc = ftalloc(struct assoc);
264                        newassoc->symbol=id;
265			newassoc->num = delta->num;
266			*LastSymbol = newassoc;
267			LastSymbol = &newassoc->nextassoc;
268                }
269        }
270	*LastSymbol = nil;
271	getsemi(Ksymbols);
272
273	getkey(Klocks);
274	LastLock = &Locks;
275        while (id = getid()) {
276                if (!getlex(COLON))
277			fatserror("missing ':' in lock");
278		if (!(delta=getdnum())) {
279			fatserror("missing number in lock");
280                } else { /*add new pair to lock list*/
281			newlock = ftalloc(struct lock);
282                        newlock->login=id;
283                        newlock->delta=delta;
284			*LastLock = newlock;
285			LastLock = &newlock->nextlock;
286                }
287        }
288	*LastLock = nil;
289	getsemi(Klocks);
290
291	if ((StrictLocks = getkeyopt(Kstrict)))
292		getsemi(Kstrict);
293
294	Comment.size = 0;
295	if (getkeyopt(Kcomment)) {
296		if (nexttok==STRING) {
297			Comment = savestring(&Commleader);
298			nextlex();
299		}
300		getsemi(Kcomment);
301        }
302
303	Expand = KEYVAL_EXPAND;
304	if (getkeyopt(Kexpand)) {
305		if (nexttok==STRING) {
306			bufautobegin(&b);
307			cb = savestring(&b);
308			if ((Expand = strn2expmode(cb.string,cb.size)) < 0)
309			    fatserror("unknown expand mode %.*s",
310				(int)cb.size, cb.string
311			    );
312			bufautoend(&b);
313			nextlex();
314		}
315		getsemi(Kexpand);
316        }
317	Ignored = getphrases(Kdesc);
318}
319
320char const *const expand_names[] = {
321	/* These must agree with *_EXPAND in rcsbase.h.  */
322	"kv","kvl","k","v","o",
323	0
324};
325
326	int
327str2expmode(s)
328	char const *s;
329/* Yield expand mode corresponding to S, or -1 if bad.  */
330{
331	return strn2expmode(s, strlen(s));
332}
333
334	static int
335strn2expmode(s, n)
336	char const *s;
337	size_t n;
338{
339	char const *const *p;
340
341	for (p = expand_names;  *p;  ++p)
342		if (memcmp(*p,s,n) == 0  &&  !(*p)[n])
343			return p - expand_names;
344	return -1;
345}
346
347
348	void
349ignorephrase()
350/* Ignore a phrase introduced by a later version of RCS.  */
351{
352	warnignore();
353	hshenter=false;
354	for (;;) {
355	    switch (nexttok) {
356		case SEMI: hshenter=true; nextlex(); return;
357		case ID:
358		case NUM: ffree1(NextString); break;
359		case STRING: readstring(); break;
360		default: break;
361	    }
362	    nextlex();
363	}
364}
365
366
367	static int
368getdelta()
369/* Function: reads a delta block.
370 * returns false if the current block does not start with a number.
371 */
372{
373        register struct hshentry * Delta, * num;
374	struct branchhead **LastBranch, *NewBranch;
375
376	if (!(Delta = getdnum()))
377		return false;
378
379        hshenter = false; /*Don't enter dates into hashtable*/
380        Delta->date = getkeyval(Kdate, NUM, false);
381        hshenter=true;    /*reset hshenter for revision numbers.*/
382
383        Delta->author = getkeyval(Kauthor, ID, false);
384
385        Delta->state = getkeyval(Kstate, ID, true);
386
387	getkey(K_branches);
388	LastBranch = &Delta->branches;
389	while ((num = getdnum())) {
390		NewBranch = ftalloc(struct branchhead);
391                NewBranch->hsh = num;
392		*LastBranch = NewBranch;
393		LastBranch = &NewBranch->nextbranch;
394        }
395	*LastBranch = nil;
396	getsemi(K_branches);
397
398	getkey(Knext);
399	Delta->next = num = getdnum();
400	getsemi(Knext);
401	Delta->lockedby = nil;
402	Delta->log.string = 0;
403	Delta->selector = true;
404	Delta->ig = getphrases(Kdesc);
405        TotalDeltas++;
406        return (true);
407}
408
409
410	void
411gettree()
412/* Function: Reads in the delta tree with getdelta(), then
413 * updates the lockedby fields.
414 */
415{
416	struct lock const *currlock;
417
418        while (getdelta());
419        currlock=Locks;
420        while (currlock) {
421                currlock->delta->lockedby = currlock->login;
422                currlock = currlock->nextlock;
423        }
424}
425
426
427	void
428getdesc(prdesc)
429int  prdesc;
430/* Function: read in descriptive text
431 * nexttok is not advanced afterwards.
432 * If prdesc is set, the text is printed to stdout.
433 */
434{
435
436	getkeystring(Kdesc);
437        if (prdesc)
438                printstring();  /*echo string*/
439        else    readstring();   /*skip string*/
440}
441
442
443
444
445
446
447	static char const *
448getkeyval(keyword, token, optional)
449	char const *keyword;
450	enum tokens token;
451	int optional;
452/* reads a pair of the form
453 * <keyword> <token> ;
454 * where token is one of <id> or <num>. optional indicates whether
455 * <token> is optional. A pointer to
456 * the actual character string of <id> or <num> is returned.
457 */
458{
459	register char const *val = nil;
460
461	getkey(keyword);
462        if (nexttok==token) {
463                val = NextString;
464                nextlex();
465        } else {
466		if (!optional)
467			fatserror("missing %s", keyword);
468        }
469	getsemi(keyword);
470        return(val);
471}
472
473
474
475
476	void
477putadmin(fout)
478register FILE * fout;
479/* Function: Print the <admin> node read with getadmin() to file fout.
480 * Assumption: Variables AccessList, Symbols, Locks, StrictLocks,
481 * and Head have been set.
482 */
483{
484	struct assoc const *curassoc;
485	struct lock const *curlock;
486	struct access const *curaccess;
487
488	aprintf(fout, "%s\t%s;\n", Khead, Head?Head->num:"");
489	if (Dbranch && VERSION(4)<=RCSversion)
490		aprintf(fout, "%s\t%s;\n", Kbranch, Dbranch);
491
492	aputs(Kaccess, fout);
493        curaccess = AccessList;
494        while (curaccess) {
495	       aprintf(fout, "\n\t%s", curaccess->login);
496               curaccess = curaccess->nextaccess;
497        }
498	aprintf(fout, ";\n%s", Ksymbols);
499        curassoc = Symbols;
500        while (curassoc) {
501	       aprintf(fout, "\n\t%s:%s", curassoc->symbol, curassoc->num);
502               curassoc = curassoc->nextassoc;
503        }
504	aprintf(fout, ";\n%s", Klocks);
505        curlock = Locks;
506        while (curlock) {
507	       aprintf(fout, "\n\t%s:%s", curlock->login, curlock->delta->num);
508               curlock = curlock->nextlock;
509        }
510	if (StrictLocks) aprintf(fout, "; %s", Kstrict);
511	aprintf(fout, ";\n");
512	if (Comment.size) {
513		aprintf(fout, "%s\t", Kcomment);
514		putstring(fout, true, Comment, false);
515		aprintf(fout, ";\n");
516        }
517	if (Expand != KEYVAL_EXPAND)
518		aprintf(fout, "%s\t%c%s%c;\n",
519			Kexpand, SDELIM, expand_names[Expand], SDELIM
520		);
521	awrite(Ignored.string, Ignored.size, fout);
522	aputc('\n', fout);
523}
524
525
526
527
528	static void
529putdelta(node,fout)
530register struct hshentry const *node;
531register FILE * fout;
532/* Function: prints a <delta> node to fout;
533 */
534{
535	struct branchhead const *nextbranch;
536
537        if (node == nil) return;
538
539	aprintf(fout, "\n%s\n%s\t%s;\t%s %s;\t%s %s;\nbranches",
540		node->num,
541		Kdate, node->date,
542		Kauthor, node->author,
543		Kstate, node->state?node->state:""
544	);
545        nextbranch = node->branches;
546        while (nextbranch) {
547	       aprintf(fout, "\n\t%s", nextbranch->hsh->num);
548               nextbranch = nextbranch->nextbranch;
549        }
550
551	aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:"");
552	awrite(node->ig.string, node->ig.size, fout);
553}
554
555
556
557
558	void
559puttree(root,fout)
560struct hshentry const *root;
561register FILE * fout;
562/* Function: prints the delta tree in preorder to fout, starting with root.
563 */
564{
565	struct branchhead const *nextbranch;
566
567        if (root==nil) return;
568
569	if (root->selector)
570		putdelta(root,fout);
571
572        puttree(root->next,fout);
573
574        nextbranch = root->branches;
575        while (nextbranch) {
576             puttree(nextbranch->hsh,fout);
577             nextbranch = nextbranch->nextbranch;
578        }
579}
580
581
582	static exiting void
583unexpected_EOF()
584{
585	faterror("unexpected EOF in diff output");
586}
587
588int putdtext(num,log,srcfilename,fout,diffmt)
589	char const *num, *srcfilename;
590	struct cbuf log;
591	FILE *fout;
592	int diffmt;
593/* Function: write a deltatext-node to fout.
594 * num points to the deltanumber, log to the logmessage, and
595 * sourcefile contains the text. Doubles up all SDELIMs in both the
596 * log and the text; Makes sure the log message ends in \n.
597 * returns false on error.
598 * If diffmt is true, also checks that text is valid diff -n output.
599 */
600{
601	RILE *fin;
602	int result;
603	if (!(fin = Iopen(srcfilename, "r", (struct stat*)0))) {
604		eerror(srcfilename);
605		return false;
606	}
607	result = putdftext(num,log,fin,fout,diffmt);
608	Ifclose(fin);
609	return result;
610}
611
612	void
613putstring(out, delim, s, log)
614	register FILE *out;
615	struct cbuf s;
616	int delim, log;
617/*
618 * Output to OUT one SDELIM if DELIM, then the string S with SDELIMs doubled.
619 * If LOG is set then S is a log string; append a newline if S is nonempty.
620 */
621{
622	register char const *sp;
623	register size_t ss;
624
625	if (delim)
626		aputc(SDELIM, out);
627	sp = s.string;
628	for (ss = s.size;  ss;  --ss) {
629		if (*sp == SDELIM)
630			aputc(SDELIM, out);
631		aputc(*sp++, out);
632	}
633	if (s.size && log)
634		aputc('\n', out);
635	aputc(SDELIM, out);
636}
637
638	int
639putdftext(num,log,finfile,foutfile,diffmt)
640	char const *num;
641	struct cbuf log;
642	RILE *finfile;
643	FILE *foutfile;
644	int diffmt;
645/* like putdtext(), except the source file is already open */
646{
647	declarecache;
648	register FILE *fout;
649	register int c;
650	register RILE *fin;
651	int ed;
652	struct diffcmd dc;
653
654	fout = foutfile;
655	aprintf(fout,DELNUMFORM,num,Klog);
656        /* put log */
657	putstring(fout, true, log, true);
658        /* put text */
659	aprintf(fout, "\n%s\n%c", Ktext, SDELIM);
660	fin = finfile;
661	setupcache(fin);
662	if (!diffmt) {
663	    /* Copy the file */
664	    cache(fin);
665	    for (;;) {
666		cachegeteof(c, break;);
667		if (c==SDELIM) aputc(SDELIM,fout);   /*double up SDELIM*/
668		aputc(c,fout);
669	    }
670	} else {
671	    initdiffcmd(&dc);
672	    while (0  <=  (ed = getdiffcmd(fin,false,fout,&dc)))
673		if (ed) {
674		    cache(fin);
675		    while (dc.nlines--)
676			do {
677			    cachegeteof(c, { if (!dc.nlines) goto OK_EOF; unexpected_EOF(); });
678			    if (c == SDELIM)
679				aputc(SDELIM,fout);
680			    aputc(c,fout);
681			} while (c != '\n');
682		    uncache(fin);
683		}
684	}
685    OK_EOF:
686	aprintf(fout, "%c\n", SDELIM);
687	return true;
688}
689
690	void
691initdiffcmd(dc)
692	register struct diffcmd *dc;
693/* Initialize *dc suitably for getdiffcmd(). */
694{
695	dc->adprev = 0;
696	dc->dafter = 0;
697}
698
699	static exiting void
700badDiffOutput(buf)
701	char const *buf;
702{
703	faterror("bad diff output line: %s", buf);
704}
705
706	static exiting void
707diffLineNumberTooLarge(buf)
708	char const *buf;
709{
710	faterror("diff line number too large: %s", buf);
711}
712
713	int
714getdiffcmd(finfile, delimiter, foutfile, dc)
715	RILE *finfile;
716	FILE *foutfile;
717	int delimiter;
718	struct diffcmd *dc;
719/* Get a editing command output by 'diff -n' from fin.
720 * The input is delimited by SDELIM if delimiter is set, EOF otherwise.
721 * Copy a clean version of the command to fout (if nonnull).
722 * Yield 0 for 'd', 1 for 'a', and -1 for EOF.
723 * Store the command's line number and length into dc->line1 and dc->nlines.
724 * Keep dc->adprev and dc->dafter up to date.
725 */
726{
727	register int c;
728	declarecache;
729	register FILE *fout;
730	register char *p;
731	register RILE *fin;
732	unsigned long line1, nlines, t;
733	char buf[BUFSIZ];
734
735	fin = finfile;
736	fout = foutfile;
737	setupcache(fin); cache(fin);
738	cachegeteof(c, { if (delimiter) unexpected_EOF(); return -1; } );
739	if (delimiter) {
740		if (c==SDELIM) {
741			cacheget(c);
742			if (c==SDELIM) {
743				buf[0] = c;
744				buf[1] = 0;
745				badDiffOutput(buf);
746			}
747			uncache(fin);
748			nextc = c;
749			if (fout)
750				aprintf(fout, "%c%c", SDELIM, c);
751			return -1;
752		}
753	}
754	p = buf;
755	do {
756		if (buf+BUFSIZ-2 <= p) {
757			faterror("diff output command line too long");
758		}
759		*p++ = c;
760		cachegeteof(c, unexpected_EOF();) ;
761	} while (c != '\n');
762	uncache(fin);
763	if (delimiter)
764		++rcsline;
765	*p = '\0';
766	for (p = buf+1;  (c = *p++) == ' ';  )
767		;
768	line1 = 0;
769	while (isdigit(c)) {
770		t = line1 * 10;
771		if (
772			ULONG_MAX/10 < line1  ||
773			(line1 = t + (c - '0'))  <  t
774		)
775			diffLineNumberTooLarge(buf);
776		c = *p++;
777	}
778	while (c == ' ')
779		c = *p++;
780	nlines = 0;
781	while (isdigit(c)) {
782		t = nlines * 10;
783		if (
784			ULONG_MAX/10 < nlines  ||
785			(nlines = t + (c - '0'))  <  t
786		)
787			diffLineNumberTooLarge(buf);
788		c = *p++;
789	}
790	if (c || !nlines) {
791		badDiffOutput(buf);
792	}
793	if (line1+nlines < line1)
794		diffLineNumberTooLarge(buf);
795	switch (buf[0]) {
796	    case 'a':
797		if (line1 < dc->adprev) {
798			faterror("backward insertion in diff output: %s", buf);
799		}
800		dc->adprev = line1 + 1;
801		break;
802	    case 'd':
803		if (line1 < dc->adprev  ||  line1 < dc->dafter) {
804			faterror("backward deletion in diff output: %s", buf);
805		}
806		dc->adprev = line1;
807		dc->dafter = line1 + nlines;
808		break;
809	    default:
810		badDiffOutput(buf);
811	}
812	if (fout) {
813		aprintf(fout, "%s\n", buf);
814	}
815	dc->line1 = line1;
816	dc->nlines = nlines;
817	return buf[0] == 'a';
818}
819
820
821
822#ifdef SYNTEST
823
824char const cmdid[] = "syntest";
825
826	int
827main(argc,argv)
828int argc; char * argv[];
829{
830
831        if (argc<2) {
832		aputs("No input file\n",stderr);
833		exitmain(EXIT_FAILURE);
834        }
835	if (!(finptr = Iopen(argv[1], FOPEN_R, (struct stat*)0))) {
836		faterror("can't open input file %s", argv[1]);
837        }
838        Lexinit();
839        getadmin();
840        putadmin(stdout);
841
842        gettree();
843        puttree(Head,stdout);
844
845        getdesc(true);
846
847	nextlex();
848
849	if (!eoflex()) {
850		fatserror("expecting EOF");
851        }
852	exitmain(EXIT_SUCCESS);
853}
854
855
856exiting void exiterr() { _exit(EXIT_FAILURE); }
857
858
859#endif
860
861