#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);
}