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

#define maxtag 16
#define lmax 512
#define bufsize 256*64

#define getch() getchar()

char lb[lmax] ;

union mix
{ 
	struct labelnode *pointer; 
	int value; 
};
struct labelnode
{ 
	struct labelnode *next; 
	union mix labels ; 
	char text[maxtag]; 
};

char block[maxtag] ;

char *infile,*outfile;
int change,verbose,exits,printtext,labels,tags,silent,checking,printchanges;
int tagsout,noline,deutsch,include_input;
FILE *input,*output;
int exitcode;
char *pos;
int line,column,pass,end;
struct labelnode *groups;

/* *** next *** */

char next(void) /* get next text position */
{
	char c;
	int k;
	c=*pos++;
	if (c=='\n')
	{
		if (noline&&(lb[0]=='\n')) {}
		else if (change&&(pass==2)) fputs(lb,output);
		if (fgets(lb,lmax,input)==0)
		{ 
			end=1; 
			return(0); 
		}
		noline=0; 
		pos=lb; 
		k=strlen(lb);
		if (k==0 || lb[k-1]!='\n')
		{
			lb[k]='\n';
			lb[k+1]=0;
		}
		c='\n';
	}
	if (printtext) printf("%c",c);
	column+=1; 
	return(c);
}

/* *** various supporting functions *** */

void skip(int l) /* skip l characters */
{
	int i;
	if (l>0)
		for (i=0;i<l;i++) next();
}

void skipblanks(void) /* skip blanks */
{
	while (*pos==' ') next();
}

void skipalnum(void) /* skip a alphanumerical word */
{
	while (isalnum(*pos)) next();
}

void skipalnumbs(void) /* skip a alphanumerical with \ */
{
	while (isalnum(*pos) || *pos=='\\') next();
}

void printlast(void) /* print last position in text */
{
	printf("\nLast position checked : Line %d, Column %d (in %s)",
		line,column,infile);
}

void wait(void)
{
	int a;
	printf("\nHit return or ESC to exit, please!\n");
	do { 
	} while((a=getch())==0);
	if (a==27) exit(1);
}

void error(void) /* error exit */
{
	printlast();
	wait();
	exitcode=1;
	if (exits)
		exit(exitcode);
}

int scannumber(void) /* scan text for an integer */
{
	int sign=0,n=0;
	if (*pos=='-') {
		sign=1; 
		next(); 
	}
	if (!(isdigit(*pos)))
	{ 
		printf("\nIllegal Number"); 
		error(); 
	}
	while (isdigit(*pos))
	{
		n=n*10+(*pos)-'0';
		next();
	}
	if (sign) return(-n);
	else return(n);
}

void putcommand(char *a,int *l)
/* puts a word at pos to a and its length to l */
{
	char *p;
	p=pos; 
	*l=0;
	if (isalpha(*p))
	{
		do { 
			*a++=*p++; 
			(*l)++; 
		} while((isalpha(*p))&&(*l<50));
		*a=0;
	}
	else
	{
		*a++=*p; 
		*a=0; 
		*l=1;
	}
}

void show(char *text) /* print text, if verbose is on */
{
	if ((verbose)&&(!printtext)) printf("%s",text);
}

/* *** find and scanfor *** */

int find(char *text) /* advance in the file, starting from pos.
              if text="", find end of file, else stop at text
              if text found, return 1, else 0.*/
{
	int l;
	void advance(void) ;
	l=strlen(text);
	do
	{
		if (l>0)
			if (strncmp(pos,text,l)==0) { 
				skip(l); 
				return(1); 
			}
		advance();
	}  while (!end);
	return(0);
}

void scanfor(char *text) /* scan till text appears */
{
	if(!(find(text))) { 
		printf("\n%s missing",text); 
		error(); 
	}
}

/* *** subroutines for german syntax *** */

void umlaut(char a)
{
	int lbl;
	char repl[3];
	if (deutsch&&(pass==2))
	{
		pos=pos-1;
		repl[0]='\\'; 
		repl[1]='"'; 
		repl[2]=a;
		lbl=strlen(lb)+1;
		if (lbl+3>=lmax)
		{ 
			printf("\nLine buffer overflow"); 
			error(); 
			exit(exitcode); 
		}
		memmove(pos+2,pos,lbl-(int)(pos-lb));
		memmove(pos,repl,3);
	};
}

void szet(void)
{
	int lbl;
	char repl[5];
	if (deutsch&&(pass==2))
	{
		pos=pos-1;
		repl[0]='{'; 
		repl[1]='\\'; 
		repl[2]='s'; 
		repl[3]='s'; 
		repl[4]='}';
		lbl=strlen(lb)+1;
		if (lbl+5>=lmax)
		{ 
			printf("\nLine buffer overflow"); 
			error(); 
			exit(exitcode); 
		}
		memmove(pos+4,pos,lbl-(int)(pos-lb));
		memmove(pos,repl,5);
	};
}

/* *** label handling commands *** */

struct labelnode *findlast(struct labelnode *r)
/* find the last label in a list */
{
	if (r==0) return(0);
	while (r->next!=0) { 
		r=r->next; 
	};
	return(r);
}

struct labelnode *findnode(struct labelnode *r,char *label)
/* scan a list of labels for label */
{
	if (r==0) return(0);
	while (strcmp(label,r->text)!=0)
	{
		if (r->next==0) return(0);
		r=r->next;
	}
	return(r);
}

struct labelnode *findlabel(char *group,char *label)
/* find a label in label list, return label */
{
	struct labelnode *r,*findnode();
	if ((r=findnode(groups,group))==0) return(0);
	r=r->labels.pointer;
	while (r!=0)
	{
		if (strcmp(label,r->text)==0) return(r);
		r=r->next;
	}
	return(0);
}

struct labelnode *newnode(struct labelnode *r,char *label)
/* connects a new label to r */
{
	struct labelnode *new;
	new=(struct labelnode *)malloc(sizeof(struct labelnode));
	if (new==0)
	{ 
		printf("\nNo more space for labels"); 
		error(); 
		exit(exitcode); 
	};
	if (r!=0) r->next=new;
	new->next=0;
	new->labels.pointer=0;
	memmove(new->text,label,16);
	return(new);
}

int newvalue(char *group)
/* find the next number in group */
{
	int count=1;
	struct labelnode *r;
	if ((r=findnode(groups,group))==0) return(count);
	r=r->labels.pointer;
	while (r!=0) { 
		count=r->labels.value; 
		r=r->next; 
	}
	return(count+1);
}

void correctlabel(char *group,char *label,char *p1,char *p2)
/* remove p1-p2 from text and add
     corresponding number */
{
	int n,l,lbl,flag;
	struct labelnode *r;
	char string[12];
	if (strcmp(group,"block")!=0)
	{
		flag=1;
		if ((r=findlabel(group,label))==0)
		{ 
			printf("\nLabel %s.%s not defined",group,label); 
			error(); 
			n=0; 
		}
		else n=r->labels.value;
	}
	else 
	{
		flag=0; 
		noline=1;
	}
	if (change)
	{
		if (flag) sprintf(string,"%d",n); 
		else string[0]=0;
		l=strlen(string);
		lbl=strlen(lb)+1;
		if (lbl+l-(int)(p2-p1)>=lmax)
		{ 
			printf("\nLine buffer overflow"); 
			error(); 
			exit(exitcode); 
		}
		memmove(p1+l,p2,lbl-(int)(p2-lb));
		memmove(p1,string,l);
		if (printchanges&&flag)
			printf("\n%s.%s changed to %d",group,label,n);
		pos+=l-(int)(p2-p1);
	}
}

void printlabels(void) /* print all used labels */
{
	struct labelnode *g,*l;
	printf("\nUsed Labels and Tags : ");
	g=groups;
	while (g!=0)
	{
		l=g->labels.pointer;
		while (l!=0)
		{
			printf("\n%s.%s = %d",g->text,l->text,l->labels.value);
			l=l->next;
		}
		g=g->next;
	}
}

void outlabels(void) /* print all used labels */
{
	struct labelnode *g,*l;
	void outopen(char *) ;
	outopen(outfile);
	g=groups;
	while (g!=0)
	{
		l=g->labels.pointer;
		while (l!=0)
		{
			fprintf(output,"#;%s.%s=%d\n",g->text,l->text,l->labels.value);
			l=l->next;
		}
		g=g->next;
	}
	fclose(output);
}

void addlabel(char *group,char *label,int n)
/* add a label to the label list, value n */
{
	struct labelnode *r,*new,dummy,*newnode(),*findnode();
	struct labelnode *findlast(),*findlabel();
	if (strcmp(group,"block")==0)
	{
		/* redefine the default block with label */
		if (strlen(label)<maxtag-2) memmove(block,label,maxtag);
		else { 
			printf("\nBlock %s illegal",label); 
			error(); 
		}
	}
	else
	{
		/* add the label */
		if(findlabel(group,label)!=0)
		{ 
			printf("\nDouble declaration of label %s.%s",group,label);
			error(); 
		};
		if (groups==0) r=groups=newnode(0,group);
		else /* there is a group already */
		{
			r=findnode(groups,group); /* look up group */
			if (r==0) r=newnode(findlast(groups),group);
		}
		if (r->labels.pointer==0) /* if new group */
		{
			new=newnode(&dummy,label); 
			r->labels.pointer=dummy.next;
		}
		else new=newnode(findlast(r->labels.pointer),label); /* append label */
		new->labels.value=n;
	}
}

/* *** tag commands *** */

int gettag(char *group,char *label,char **p1,char **p2,int *n,int *val)
/* read a tag or label, and note start and end of labeltext */
{
	int br;
	char *p,*ph;
	skipblanks();
	if (*pos=='!' || *pos=='#')
	{ 
		*p1=pos; 
		if (*pos=='!') next(); 
		*p2=pos; 
		*label=0; 
		*group=0; 
		return(1); 
	}
	if (*pos=='{') { 
		next(); 
		br=1; 
	} else br=0;
	p=ph=*p1=pos;
	if (*pos=='@') next();
	while (*pos!='}')
	{
		skipalnumbs();
		if (*pos!='.') break;
		ph=pos; 
		next(); 
		*p1=pos;
	}
	skipalnum(); 
	*p2=pos;
	if (((*p2-*p1)>maxtag-1)||((ph-p)>maxtag-2)) return(0);
	if (ph!=p) { 
		memmove(group,p,(int)(ph-p)); 
		*(group+(int)(ph-p))=0; 
	}
	else memmove(group,block,maxtag-1);
	memmove(label,*p1,(int)(*p2-*p1)); 
	*(label+(int)(*p2-*p1))=0;
	if (*val&&(*pos=='=')) { 
		next(); 
		*n=scannumber(); 
		*val=1; 
		*p2=pos; 
	}
	else *val=0;
	if (*pos=='_') { 
		next(); 
		*p2=pos; 
	}
	if (br)
		if (*pos!='}') return(0);
		else next();
	return(1);
}

/* *** subroutines to process special syntax features *** */

void brackets(void) /* process {...} */
{
	int l,c;
	l=line; 
	c=column;
	show("{ ");
	if(!(find("}")))
	{ 
		printf("\n{ unbalanced in line %d, column %d",l,c); 
		error(); 
	}
	else show("} ");
}

void informula(void) /* process $...$ */
{
	int l,c;
	show("+$ ");
	l=line; 
	c=column;
	if (!(find("$")))
	{ 
		printf("\nUnmatched $ in line %d, column %d",l,c); 
		error(); 
	};
	if (*pos=='$')
	{ 
		printf("\n$$ not allowed here"); 
		error(); 
	}
	else show("-$ ");
}

void formula(void) /* process $$...$$ */
{
	int l,c;
	show("+$$ ");
	l=line; 
	c=column;
	if (!(find("$")) || *pos++!='$')
	{ 
		printf("\nUnmatched $$ in line %d, column %c",l,c); 
		error(); 
	};
	if (*pos=='$')
	{ 
		printf("$$$ is illegal"); 
		error(); 
	}
	else show("-$$ ");
}

void comment(void) /* pass a coment %... */
{
	while (*pos!='\n') { 
		next(); 
	};
}

void label(void) /* process a label, i.e note the label #=... or change #: to
           a number, depending on pass, collect #; and cancel */
{
	char group[maxtag],label[maxtag],a,*p,*p1,*p2;
	int n,val;
	p=pos; 
	a=*pos; 
	next();
	if ((a=='=')||(a==':')||(a==';'))
	{
		if (a==':') val=0; 
		else val=1; /* =value allowed? */
		if(!(gettag(group,label,&p1,&p2,&n,&val)))
		{ 
			printf("\nIllegal Label"); 
			error(); 
			exit(exitcode); 
		};
		if (pass==1)
			if ((a=='=')||(a==';'))
			{
				if (!val) n=newvalue(group); /* n was not found in =n */

				addlabel(group,label,n);
			}
		if (pass==2)
		{
			if (strcmp(group,"block")==0) addlabel(group,label,0);
			if (a==';') { 
				correctlabel("block",label,p-1,pos); 
				noline=1; 
			}
			else correctlabel(group,label,p-1,pos);
		}
	}
}

/* *** command processing functions *** */

void tag(void) /* process \tag {...} or \tag ... */
{
	char label[maxtag],group[maxtag],*p1,*p2;
	int n,val=1;
	if (tags)
	{
		*group='@';
		if (!(gettag(group+1,label,&p1,&p2,&n,&val)))
		{ 
			printf("\nIllegal Tag"); 
			error(); 
			exit(exitcode); 
		};
		if ((pass==1)&&(*p1!='!')&&(*p1!='#'))
		{
			if (!val) n=newvalue(group); /* =n not found */
			addlabel(group,label,n);
		}
		else if (pass==2)
			if ((*p1!='!')&&(*p1!='#'))
				correctlabel(group,label,p1,p2);
			else correctlabel("block",label,p1,p2);
	}
}

void thetag(void)
{
	char label[maxtag],group[maxtag],*p1,*p2;
	int n,val=0;
	if (tags)
	{
		*group='@';
		if (!(gettag(group+1,label,&p1,&p2,&n,&val)))
		{ 
			printf("\nIllegal Tag"); 
			error(); 
			exit(exitcode); 
		};
		if (pass==2)
			if ((*p1!='!') && (*p1!='#'))
				correctlabel(group,label,p1,p2);
			else correctlabel("block",label,p1,p2);
	}
}

void inopen(char *filename);
void doinput (void)
{
	FILE *oldfile;
	char name[512],*n,*oldinfile;
	int c,oldline;
	oldfile=input;
	oldinfile=infile;
	oldline=line;
	next();
	n=name;
	while (*pos!=' ' && *pos!='}' && *pos!='\n')
	{
		*n++=next();
	}
	*n++=0;
	inopen(name);
	line=0;
	infile=name;
	find("");
	fclose(input);
	input=oldfile;
	pos=lb;
	noline=1;
	*pos='\n';
	infile=oldinfile;
	line=oldline;
	end=0;
}

void command(void) /* process a command \... */
{
	char com[50];
	int l;
	putcommand(com,&l);
	skip(l);
	if (strcmp(com,"tag")==0) tag();
	else if (strcmp(com,"thetag")==0) thetag();
	else if (strcmp(com,"input")==0 && include_input) doinput();
}

/* *** subroutines to process the file *** */

void advance(void) /* advance pos one character or command */
{
	char c;
	c=next();
	if (checking)
		switch(c)
		{
		case '\n': 
			{ 
				line+=1; 
				column=1;
				if (verbose) printf(" %d ",line); 
				break; 
			}
		case '{': 
			{ 
				brackets(); 
				break; 
			}
		case '}': 
			{ 
				printf("\nUnmatched }"); 
				error(); 
				break; 
			}
		case '$':
			{ 
				if (*pos=='$')
				{
					next();
					if (*pos=='$')
					{ 
						printf("$$$ is illegal"); 
						error(); 
					}
					else formula();
				}
				else informula();
				break;
			}
		case '\\': 
			{ 
				command(); 
				break; 
			}
		case '%': 
			{ 
				comment(); 
				break; 
			}
		case '#': 
			{ 
				if (labels) label(); 
				break; 
			}
		case '”':
		case 'ö': 
			{ 
				umlaut('o'); 
				break; 
			}
		case '„':
		case 'ä': 
			{ 
				umlaut('a'); 
				break; 
			}
		case '':
		case 'ü': 
			{ 
				umlaut('u'); 
				break; 
			}
		case '™':
		case 'Ö': 
			{ 
				umlaut('O'); 
				break; 
			}

		case 'Ž':
		case 'Ä': 
			{ 
				umlaut('A'); 
				break; 
			}
		case 'š':
		case 'Ü': 
			{ 
				umlaut('U'); 
				break; 
			}
		case 'ž': 
		case 'á':
		case 'ß': 
			{ 
				szet(); 
				break; 
			}
		default: 
			{ 
			};
		}
	else
		switch(c)
		{
		case '#': 
			{ 
				if (labels) label(); 
				break; 
			}
		case '\n' : 
			{ 
				column=1; 
				line+=1;
				if (verbose) printf("%d ",line); 
				break; 
			}
		default: 
			{ 
			};
		}
}

/* *** main subroutines *** */

void getparams(int n,char *a[]) /* read parameters from command line */
{
	char *i;
	if ((n<3)||(*a[1]!='-'))
	{
		printf("\nUsage -vpltsne file1.tex file2.tex");
		printf("\nv=verbose, p=print, l=labels, t=tags");
		printf("\ns=silent, n=no checking e=don't exit on error");
		printf("\no=output tags and labels, d=deutsch, i=include input");
		wait(); 
		exit(1);
	}
	verbose=printtext=labels=tags=silent=deutsch=include_input=0;
	exits=checking=1;
	for (i=a[1]+1;*i!=0;i++)
		switch(toupper(*i))
		{
		case 'V': 
			{ 
				verbose=1; 
				break; 
			}
		case 'P': 
			{ 
				printtext=1; 
				verbose=0; 
				break; 
			}
		case 'L': 
			{ 
				labels=1; 
				break; 
			}
		case 'T': 
			{ 
				tags=1; 
				break; 
			}
		case 'S': 
			{ 
				silent=1; 
				break; 
			}
		case 'N': 
			{ 
				checking=0; 
				break; 
			}
		case 'E': 
			{ 
				exits=0; 
				break; 
			}
		case 'O': 
			{ 
				tagsout=1; 
				break; 
			}
		case 'D': 
			{ 
				deutsch=1; 
				break; 
			}
		case 'I':
			{
				include_input=1;
				break;
			}
		default: 
			{ 
			}
		}
	infile=a[2];
	if (n<4) change=0; 
	else {
		outfile=a[3]; 
		change=1;
	};
	if (tagsout) change=0 ;
}

void inopen(char *filename)
{
	char name[512];
	if ((input=fopen(filename,"r"))==0)
	{ 
		strcpy(name,filename);
		strcat(name,".tex");
		if ((input=fopen(name,"r"))==0)
		{
			printf("\nUnable to open %s",filename); 
			wait(); 
			exit(1);
		} 
	};
	setvbuf(input,0,_IOFBF,bufsize);
	lb[0]='\n'; 
	pos=lb; 
	noline=1;
}

void outopen(char *filename)
{
	if ((output=fopen(filename,"w"))==0)
	{ 
		printf("\nUnable to open %s",filename); 
		wait(); 
		exit(1); 
	};
	setvbuf(output,0,_IOFBF,bufsize);
}

void checkfile(void) /* check the syntax of the file at filestart */
{
	printf("\nPass 1 : \n"); /* line swap on */
	line=0; 
	column=1; 
	groups=0; 
	block[0]=0;
	pass=1; 
	end=0; 
	inopen(infile);
	if (labels||tags) find("");
	if ((labels||tags)&&(!silent)) printlabels();
	if (tagsout) outlabels() ;
	else
	{
		pass=2; 
		line=0; 
		column=1; 
		printtext=0;
		printchanges=verbose; 
		verbose=0; 
		end=0; 
		block[0]=0;
		rewind(input); 
		lb[0]='\n'; 
		pos=lb; 
		noline=1;
		if (change) outopen(outfile);
		printf("\nPass 2 : \n"); 
		find("");
		if (change) fclose(output) ;
	}
	printlast();
	fclose(input) ;
	printf("\n\n");
}

main(int argc,char *argv[]) /* main function to read in, process and write file */
{
	printf(
	    "\n *****   Checker,  Copyright by R. Grothmann, Version %s  ***** \n",
	    __DATE__);
	getparams(argc,argv);
	if (change) printf("\nModifying %s to %s.",infile,outfile);
	else printf("\nChecking %s.",infile);
	checkfile();
	return(exitcode);
}