/*
 * rail.c - railroad diagram utility
 *
 * 09-Jul-1990 L. Rooijakkers
 * 09-Oct-1990 L. Rooijakkers
 * 07-Feb-1991 L. Rooijakkers	indexing, embedded options
 * 08-Feb-1991 L. Rooijakkers	small mods for version 1.0 #0
 * 12-Feb-1991 L. Rooijakkers	minor portability fixes
 * 28-Jun-1994 K. Barthelmann	minor changes
 * 12-Jan-1997 K. Barthelmann	minor portability improvements
 * 19-May-1998 J. Olsson        Added suport for arrow heads.
 *
 */

#ifndef lint
static char SccsId[]="@(#)rail 26-Jul-1998";
#endif

#define USAGE	"usage: %s [-+acdit] [file]\n"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "patchlevel.h"
#include "rail.h"
#include "gram.h"

static char *myname;	/* program name */

static char *file;	/* input file */
extern FILE *yyin;	/* lex input stream */

static char *outfile;	/* output file */
FILE *outf;		/* output stream */

static int newline;	/* stdout requires a newline */

static IDTYPE *idlist;	/* identifier list */

IDTYPE *errorid;	/* identifier for errors */

/* options */

int altstar;		/* alternate star */
int arrowheads;         /* produce arrowheads on the rails */
static int chkgram;	/* check grammar */
static int treelist;	/* list rule trees */
static int genindex;	/* generate index entry for left hand sides */

#ifdef YYDEBUG
extern int yydebug;	/* show yacc debugging */
#endif

int anonymous;		/* anonymous rules */

main(argc,argv)
unsigned argc;
char *argv[];
{
	char *arg, **argp;
	unsigned len;

	printf("This is Rail, Version %s #%d\n",VERSION,PATCHLEVEL);
	newline=0;

	myname=argv[0];

	for(argp = &argv[1];(arg = *argp)!=NULL;argp++) {
		if(*arg!='-' && *arg!='+')
			break;
		if(setopt(*arg,arg+1)==0)
			usage();
	}

	if(arg!=NULL && *++argp!=NULL)
		usage();

	if(arg==NULL) {
		file="stdin";
		outfile="stdout";
		yyin=stdin;
		outf=stdout;
	} else {
		len=strlen(arg)+5;
		file=mcheck(malloc(len));
		outfile=mcheck(malloc(len));
		strcat(strcpy(file,arg),".rai");
		strcat(strcpy(outfile,arg),".rao");
		if((yyin=fopen(file,"r"))==NULL) {
			perror(file);
			exit(1);
		}
		if((outf=fopen(outfile,"w"))==NULL) {
			perror(outfile);
			exit(1);
		}
	}

	printf("(%s",file);
	newline=1;

	line=1;

	fprintf(outf,"%% This file was generated by '%s' from '%s'\n",myname,file);

	if(yyparse()!=0)
		exit(1);

	printf(")\n");
	newline=0;

	file=NULL;

	if(chkgram) {
		checkdefs();
		if(anonymous)
			error("unnamed rules are present",(char *)NULL);
	}

	exit(0);
	/*NOTREACHED*/
}

int setopt(c,s)
char c, *s;
{
	int set;

	set = c=='-';

	while(*s!='\0') {

		switch(*s++) {

		case 'a':
			altstar=set;
			break;

		case 'c':
			chkgram=set;
			break;

		case 'd':
#ifdef YYDEBUG
			yydebug=set;
#endif
			break;

		case 'i':
			genindex=set;
			break;

		case 't':
			treelist=set;
			break;

		case 'h':
		        arrowheads=set;
			break;

		default:
			return 0;
		}
	}

	return 1;
}

usage()
{
	fprintf(stderr,USAGE,myname);
	exit(1);
}

/* error routine for yyparse() */

yyerror(s)
char *s;
{
	fatal("%s",s);
}

/* wrap-up routine for yylex() */

yywrap()
{
	return(1);
}

/* check for non-NULL pointer */

char *mcheck(s)
char *s;
{
	if(s==NULL)
		fatal("out of memory",(char *)NULL);

	return(s);
}

/* make a new body */

BODYTYPE *newbody(kind,body1,body2)
int kind;
BODYTYPE *body1, *body2;
{
	BODYTYPE *body;

	body=(BODYTYPE *)mcheck(malloc(sizeof(BODYTYPE)));
	body->kind=kind;
	body->nlist=0;
	body->done=0;
	body->id=NULL;
	body->text=NULL;
	body->annot=NULL;

	if(body1!=NULL)
		body->list[body->nlist++]=body1;

	if(body2!=NULL)
		body->list[body->nlist++]=body2;

	return(body);
}

/* free a body recursively */

freebody(body)
BODYTYPE *body;
{
	int i;

	if(body==NULL)
		return;

	for(i=0;i<body->nlist;i++)
		freebody(body->list[i]);

	if(body->text!=NULL) {
		/* should free text here */
	}

	free((char *)body);
}

/* test if a body is empty */

int isemptybody(body)
BODYTYPE *body;
{
	return(body==NULL || body->kind==EMPTY);
}

/* add to a body list */

static addlist(body1,body2)
BODYTYPE *body1, *body2;
{
	if(body1->nlist>=MAXLIST) {
		yyerror("list too long");
	} else
		body1->list[body1->nlist++]=body2;
}

/* add two body lists (for CAT, BAR, PLUS) */

BODYTYPE *addbody(kind,body1,body2)
int kind;
BODYTYPE *body1, *body2;
{
	BODYTYPE *body;
	int i;

	if(body1->kind==kind && !(kind==BAR && body1->done) ) {
		body=body1;
	} else {
		body=newbody(kind,NULLBODY,NULLBODY);
		addlist(body,body1);
	}

	if(body2->kind==kind && !(kind==BAR && body2->done) ) {
		for(i=0;i<body2->nlist;i++)
			addlist(body,body2->list[i]);
		free((char *)body2);
	} else {
		addlist(body,body2);
	}

	return(body);
}

/* reverse all concatenations (for PLUS) */

BODYTYPE *revbody(body)
BODYTYPE *body;
{
	int i,j;
	BODYTYPE *tmp;

	if(body->kind==CAT) {
		for(i=0,j=body->nlist-1;i<j;i++,j--) {
			tmp=body->list[i];
			body->list[i]=body->list[j];
			body->list[j]=tmp;
		}
	}

	for(i=0;i<body->nlist;i++)
		body->list[i]=revbody(body->list[i]);

	return(body);
}

/* print a body for debugging */

prtbody(indent,body)
int indent;
BODYTYPE *body;
{
	int i;

	fprintf(outf,"%% ");

	for(i=0;i<indent;i++)
		fprintf(outf,". ");

	if(body==NULL)
		fprintf(outf,"NULL\n");
	else {
		switch(body->kind) {

		case EMPTY:
			fprintf(outf,"EMPTY");
			break;

		case CAT:
			fprintf(outf,"CAT");
			break;

		case BAR:
			fprintf(outf,"BAR");
			break;

		case PLUS:
			fprintf(outf,"PLUS");
			break;

		case ANNOTE:
			fprintf(outf,"ANNOTE [%s]",body->text);
			break;

		case IDENT:
			fprintf(outf,"IDENT %s",body->id->name);
			break;

		case CR:
			fprintf(outf,"CR");
			break;

		case STRNG:
			fprintf(outf,"STRNG '%s'",body->text);
			break;

		default:
			fprintf(outf,"UNKNOWN");
			break;
		}

		fprintf(outf," y=%d nexty=%d\n",body->ystart,body->ynext);

		for(i=0;i<body->nlist;i++)
			prtbody(indent+1,body->list[i]);
	}
}

/* output a body */

outbody(id,body)
IDTYPE *id;
BODYTYPE *body;
{
	posbody(body,0);

	if(treelist)
		prtbody(0,body);

	fprintf(outf,"\\rail@begin{%d}{",body->ynext);
	if(id!=NULL) {
		fprintf(outf,"%s",id->name);
	}
	fprintf(outf,"}\n");

	if (arrowheads) {
	  fmtbody(body,"",RIGHT_ARROW);
	  fprintf(outf,"\\rail@vend\n");
	}
	else {
	  fmtbody(body,"",NO_ARROW);
	  fprintf(outf,"\\rail@end\n");
	}
}

/* format a body */

fmtbody(body,cent,arrow)
BODYTYPE *body;
char *cent;
char arrow;
{
	BODYTYPE *body1;
	int i;
	char *next;

	switch(body->kind) {

	case EMPTY:
		break;

	case CAT:
		for(i=0;i<body->nlist;i++)
			fmtbody(body->list[i],"",arrow);
		break;

	case BAR:
		next="\\rail@bar\n";
		for(i=0;i<body->nlist;i++) {
			body1=body->list[i];
			fprintf(outf,next,body1->ystart);
			next="\\rail@nextbar{%d}\n";
			fmtbody(body1,"",arrow);
		}
		fprintf(outf,"\\rail@endbar\n");
		break;

	case PLUS:
		fprintf(outf,"\\rail@plus\n");

		if (arrow == NO_ARROW)
		  fmtbody(body->list[0],"",arrow);
		else if (body->list[0]->kind == EMPTY) {
		  arrow = arrow == RIGHT_ARROW ? LEFT_ARROW : RIGHT_ARROW;
		  fmtbody(body->list[0],"",arrow);
		}
		else {
		  fmtbody(body->list[0],"",arrow);
		  arrow = arrow == RIGHT_ARROW ? LEFT_ARROW : RIGHT_ARROW;
		}

		body1=body->list[1];
		fprintf(outf,"\\rail@nextplus{%d}\n",body1->ystart);

		fmtbody(body1,"c",arrow);

		fprintf(outf,"\\rail@endplus\n");
		break;

	case ANNOTE:
		fprintf(outf,"\\rail@annote[%s]\n",body->text);
		break;

	case IDENT:
		if(body->id->kind==TERM)
		  if (arrow == NO_ARROW)
		    fprintf(outf,"\\rail@%stoken{%s}",cent,body->id->name);
		  else
		    fprintf(outf,"\\rail@%c%stoken{%s}",arrow,cent,body->id->name);
		else
		  if (arrow == NO_ARROW)
		    fprintf(outf,"\\rail@%snont{%s}",cent,body->id->name);
		  else
		    fprintf(outf,"\\rail@%c%snont{%s}",arrow,cent,body->id->name);

		fprintf(outf,"[%s]\n",body->annot!=NULL?body->annot:"");
		break;

	case CR:
		fprintf(outf,"\\rail@cr{%d}\n",body->ynext-1);
		break;

	case STRNG:
		if (arrow == NO_ARROW)
		  fprintf(outf,"\\rail@%sterm{%s}",cent,body->text);
		else
		  fprintf(outf,"\\rail@%c%sterm{%s}",arrow,cent,body->text);
		fprintf(outf,"[%s]\n",body->annot!=NULL?body->annot:"");
		break;

	default:
		fprintf(outf,"\\rail@unknown\n");
		break;
	}
}

/* position body (fill in height and ystart) */

posbody(body,ystart)
BODYTYPE *body;
{
	BODYTYPE *body1;
	int i;

	switch(body->kind) {

	case CAT:
		body->ystart=ystart;
		body->ynext=ystart+1;

		for(i=0;i<body->nlist;i++) {
			body1=body->list[i];
			if(body1->kind==CR) {
				body1->ystart=ystart;
				body1->ynext=body->ynext+2;
				ystart=body1->ynext-1;
			} else
				posbody(body1,ystart);
			if(body1->ynext>body->ynext)
				body->ynext=body1->ynext;
		}
		break;

	case BAR:
	case PLUS:
		body->ystart=ystart;
		body->ynext=ystart+1;

		for(i=0;i<body->nlist;i++) {
			body1=body->list[i];
			posbody(body1,ystart);
			ystart=body1->ynext;
			if(body1->ynext>body->ynext)
				body->ynext=body1->ynext;
		}
		break;

	case CR:
		body->ystart=ystart;
		body->ynext=ystart+3;
		break;

	case EMPTY:
	case ANNOTE:
	case IDENT:
	case STRNG:
	default:
		body->ystart=ystart;
		body->ynext=ystart+1;
		break;
	}
}

/* output an index entry */

outindex(id)
IDTYPE *id;
{
	if(id!=NULL)
		fprintf(outf,"\\rail@index{%s}\n",id->name);
}

/* make a new rule list */

RULETYPE *newrule(id,body)
IDTYPE *id;
BODYTYPE *body;
{
	RULETYPE *rule;

	rule=(RULETYPE *)mcheck(malloc(sizeof(RULETYPE)));
	rule->id=id;
	rule->body=body;
	rule->next=NULL;

	return(rule);
}

/* free a rule list */

freerule(rule)
RULETYPE *rule;
{
	RULETYPE *rulep;

	while(rule!=NULL) {
		rulep=rule->next;
		freebody(rule->body);
		free((char *)rule);
		rule=rulep;
	}
}

/* add two rule lists */

RULETYPE *addrule(rule1,rule2)
RULETYPE *rule1, *rule2;
{
	RULETYPE *rulep;

	if(rule1==NULL)
		return(rule2);

	for(rulep=rule1;rulep->next!=NULL;rulep=rulep->next)
		;

	rulep->next=rule2;

	return(rule1);
}

/* output a rule list */

outrule(rule)
RULETYPE *rule;
{
	while(rule!=NULL) {

		if(genindex)
			outindex(rule->id);

		outbody(rule->id,rule->body);

		rule=rule->next;
	}
}

/* look up an identifier */

IDTYPE *lookup(name)
char *name;
{
	IDTYPE *idp, **idq;

	for(idq = &idlist;(idp = *idq)!=NULL;idq = &idp->next)
		if(strcmp(name,idp->name)==0)
			return(idp);

	idp=(IDTYPE *)mcheck(malloc(sizeof(IDTYPE)));

	idp->name=mcheck(malloc((unsigned)strlen(name)+1));
	strcpy(idp->name,name);
	idp->next=NULL;
	idp->kind=UNKNOWN;

	*idq=idp;

	return(idp);
}

/* delete an identifier */

delete(id)
IDTYPE *id;
{
	IDTYPE *idp, **idq;

	for(idq = &idlist;(idp = *idq)!=NULL;idq = &idp->next) {
		if(idp==id) {
			*idq = idp->next;
			free(idp->name);
			free((char *)idp);
			return;
		}
	}
}

/* check that there are no undefined identifiers */

checkdefs()
{
	IDTYPE *id;

	for(id=idlist;id!=NULL;id=id->next)
		if(id->kind==TOKEN)
			undef(id);
}

/* complain about an undefined identifier */

undef(id)
IDTYPE *id;
{
	if(chkgram)
		error("undefined identifier '%s'",id->name);
}

/* complain about a redefined identifier */

redef(id)
IDTYPE *id;
{
	if(chkgram)
		error("redefined identifier '%s'",id->name);
}

/* display an error */

error(f,s)
char *f, *s;
{
	if(newline) {
		printf("\n");
		newline=0;
	}

	if(file!=NULL)
		fprintf(stderr,"%s, line %u: ",file,line);

	fprintf(stderr,f,s);

	if(errorid!=NULL)
		fprintf(stderr," in rule '%s'",errorid->name);

	fprintf(stderr,"\n");
}

fatal(f,s)
char *f,*s;
{
	error(f,s);

	if(outf!=NULL) {
		fclose(outf);
		unlink(outfile);
	}
}