/***************************************************************************/
/*                                                                         */
/*  oriya.c v0.99                                                          */
/*                                                                         */
/*  Source code for "Oriya-TeX" preprocessor.                              */
/*  Anshuman Pandey <[email protected]>, 2003/01/22                 */
/*                                                                         */
/*  Based on Revision 1.1 1996/03/05 of skt.c preprocessor developed by    */
/*  Charles Wikner <[email protected]>                               */
/*                                                                         */
/***************************************************************************/

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

/* DECLARE FUNCTIONS */
void   exit        (int);
void   search      (void);
void   write_outbuf(void);
void   write_line  (char *);
char * str_find    (char *, char *);
void   getline     (void);
char * command     (char *);
void   error       (char *, int);
void   process     (void);
void   chrcat      (char *, char);
void   bncont      (void);
void   bnword      (void);
void   single      (char);
void   frontac     (void);
void   backac      (void);
void   sam_warning (void);
void   samyoga     (void);

FILE *infile, *outfile, *fopen();
char infilename[80];
char outfilename[80];

#define TRUE 1
#define FALSE 0

unsigned char bnline;     /* flag TRUE if there is any Oriya on this line     */
unsigned char bnmode;     /* flag TRUE while within {\orx }                   */
unsigned char eof_flag;   /* flag True when end of file detected              */
unsigned char ac_flag;    /* flag TRUE while processing Oriya vowels          */
unsigned char roman_flag; /* flag TRUE if previous output was Roman string    */

int nest_cnt;             /* '{' increments, '}' decrements, while in \orx    */
int err_cnt;              /* incremented by any error while in \orx           */
#define err_max 10        /* after err_max errors, program aborts             */
int line_cnt;             /* line number of current input line                */

char inbuf[133];          /* input file line buffer of text being processed   */
char *i_ptr;              /* general pointer to input buffer                  */
char outbuf[512];         /* output file line buffer of text processed        */
char *o_ptr;              /* general pointer to output buffer                 */

unsigned char cont_end;   /* flag TRUE when line ends with %-continuation     */
unsigned char cont_begin; /* flag TRUE when line begins after %-continuation  */
unsigned char hal_flag;   /* flag TRUE when hal_type detected in syllable     */
unsigned char ac_char;    /* storage for working vowel character              */
unsigned char pre_ra;     /* storage/flag for 'r' at beginning of samyoga     */
char ac_hook;             /* vowel diacritic code                             */
char bnbuf[255];          /* storage for Oriya in internal code             */
char *s_ptr;              /* general pointer to Oriya buffer                */
char *old_sptr;           /* points to samyoga start; used by warning message */
char work[80];            /* general scratchpad                               */
char *w_ptr;              /* general pointer to work buffer                   */
char tmp[80];             /* temporary buffer for previous syllable           */
int  ra;                  /* post_ra type to use with this character          */
int  ya;                  /* post_ya type to use with this character          */
int  va;                  /* post_va type to use with this character          */
int  na;                  /* post_na type to use with this character          */
int  ma;                  /* post_ma type to use with this character          */
int  la;                  /* post_la type to use with this character          */
int  post_ra;             /* flag to append ra to samyoga                     */
int  post_ya;             /* flag to append ya to samyoga                     */
int  post_va;             /* flag to append va to samyoga                     */
int  post_na;             /* flag to append na to samyoga                     */
int  post_ma;             /* flag to append ma to samyoga                     */
int  post_la;             /* flag to append la to samyoga                     */
int  hasanta;             /* flag to add hasanta to samyoga (i.e. no vowel)   */
int  hr_flag;             /* flag indicates vowel picked up in samyoga (h.r)  */
int low;


/***************************************************************************/
/* Function: main()                                                        */
/***************************************************************************/

main(argc,argv)
int argc;
char *argv[];
{ char *p; int k;

 /* Initialization */

 bnmode = eof_flag = FALSE;
 nest_cnt = err_cnt = 0;
 line_cnt = 0;
 i_ptr = inbuf;  *i_ptr = '\0';
 s_ptr = bnbuf; *s_ptr = '\0';
 o_ptr = outbuf; *o_ptr = '\0';

 /* handle command-line options */

 k=0;
 if (argc>1) strcpy(infilename,argv[1]);
 if (strcmp(infilename,"-h")==0)
 { k=1;
   strcpy(infilename,"");
   printf("Preprocessor for \"Oriya-TeX\" package v.099, 2003.01.22\n");
   printf("Anshuman Pandey <[email protected]>\n");
   printf("Syntax: oriya infile[.or] [outfile[.tex]]\n");
   exit(0);
 }

 /* then get file names */
 switch(argc-k)
 { case 3:  strcpy(infilename,argv[1+k]);
            strcpy(outfilename,argv[2+k]);
            break;
   case 2:  strcpy(infilename,argv[1+k]);
            strcpy(outfilename,"");
            break;
   default: strcpy(infilename,"");
            while(strlen(infilename) == 0)
            { printf("Input file: "); gets(infilename); }
            printf("Output file: ");
            gets(outfilename);
 }

 if (strlen(outfilename) == 0)
   { strcpy (outfilename,infilename);   /* default output file name */
     p = strchr(outfilename,'.');
     if (p != 0) *p = '\0';   /* delete out file name extension */
   }
 p = strchr(infilename,'.');
 if (p == 0) strcat(infilename,".or");  /* default input file extension */
 if ((infile=fopen(infilename,"r")) == NULL)
       { printf("Cannot open file %s\n",infilename); exit(1); }
 getline(); if (eof_flag)
       { printf("Input file %s is empty.\n",infilename); exit(1); }
 p = strchr(outfilename,'.');
 if (p == 0)
   { if (inbuf[0] == '@') strcat(outfilename,".dn");
     else strcat(outfilename,".tex"); /* set default output file extension */
   }
 if ((outfile=fopen(outfilename,"w")) == NULL)
       { printf("Cannot open output file %s\n",outfilename); exit(1); }

 /* Normal main loop */

 while(eof_flag == 0)
   { while(!bnmode && !eof_flag) search();  /* search for \orx command */
     while( bnmode && !eof_flag) process(); /* process Oriya text */
     if (err_cnt >= err_max)
        { printf("Too many (%d) errors, aborting program\n",err_cnt); break; }
   }
 if ((err_cnt < err_max) && (nest_cnt != 0))
    printf("Brace mismatch within \\orx = %d\n",nest_cnt);
 fclose(infile);
 fclose(outfile);
 exit(1);

}


/***************************************************************************/
/* Function: search()                                                      */
/*                                                                         */
/* Search inbuf for '{\orx', getting more input lines as necessary          */
/* until string found or end of file, copying input to output; if          */
/* the string is found but command not recognised, it is treated as        */
/* ordinary text; if valid command i_ptr points to first sanskrit          */
/* char after command, and sets bnmode TRUE.                              */
/***************************************************************************/

void search(void)
{
unsigned char c;
char *p,*q;
 while (eof_flag == 0)
   { p = str_find(i_ptr,"{\\orx");
     if (p == 0)
       { if (bnline == TRUE) { strcat(outbuf,i_ptr); write_outbuf(); }
         else { write_line(inbuf); o_ptr = outbuf; *o_ptr = '\0';  }
         getline();
         continue;
       }
     q = i_ptr; i_ptr = p;
     if ((p = command(p)) == 0)        /* test command string \orx */
       { p = i_ptr; i_ptr = q;         /* if bad \orx command */
         c = *++p; *p = '\0';          /* copy partial line, and search more */
         strcat(outbuf,i_ptr); *p = c; i_ptr = p; continue;
       }
     i_ptr = q;
     nest_cnt++; c = *p; *p = '\0';    /* skip over '{\orx' */
     strcat(outbuf,i_ptr);             /* append partial line to outbuf */
     *p = c; i_ptr = p;
     bnmode = TRUE; bnline = TRUE;   /* now comes the fun! */
     break;
   }
}


/***************************************************************************/
/* Function: write_outbuf()                                                */
/*                                                                         */
/* Write outbuf in 80 character lines                                      */
/***************************************************************************/

void write_outbuf(void)
{
char c, d, e;
 while(1)
 { c = '\0';
   if (strlen(outbuf) < 81) { write_line(outbuf); break; }
   for (o_ptr = outbuf + 78;     o_ptr > outbuf + 50;     o_ptr--)
       { if (*o_ptr == ' ') break; }
   if (*o_ptr != ' ') { for (o_ptr = outbuf+78; o_ptr > outbuf + 50; o_ptr--)
                             if ((*o_ptr=='\\') && (*(o_ptr-1)!='\\')) break;
                        if (o_ptr == outbuf+50) o_ptr = outbuf+78;
                        c = *o_ptr; *o_ptr++ = '%'; d = *o_ptr;
                      }
   *o_ptr++ = '\n'; e = *o_ptr; *o_ptr = '\0';
   write_line(outbuf);
   *o_ptr = e;
   if (c!='\0') { *--o_ptr = d; *--o_ptr = c; } /* restore displaced chars */
   strcpy(outbuf,o_ptr);
 }
 o_ptr = outbuf;
 *o_ptr = '\0';
}


/***************************************************************************/
/* Function: write_line()                                                  */
/*                                                                         */
/* Write p-string to output device                                         */
/***************************************************************************/

void write_line(char *p)
{
 if (err_cnt == 0) fputs(p,outfile);
}


/***************************************************************************/
/* Function: str_find()                                                    */
/*                                                                         */
/* Find first occasion of string *str within *buf before '%' char;         */
/* return pointer first char of str within buf, else 0.                    */
/***************************************************************************/

char * str_find(char *buf, char *str)
{ char *p, *x;
 p = strstr(buf,str);
 if (p == 0) return(0);
 x = strchr(buf,'%');
 if ((x != 0) && (p > x)) return(0);
 return(p);
}


/***************************************************************************/
/* Function: getline()                                                     */
/*                                                                         */
/* Get another line from input file; reset i_ptr, increments               */
/* line_cnt, and sets eof_flag if EOF.                                     */
/***************************************************************************/

void getline(void)
{
char *p;
 i_ptr = inbuf;
 *i_ptr = '\0';
 line_cnt++;
 if (fgets(inbuf,133,infile) == NULL) eof_flag = TRUE;
 if (bnmode == FALSE) bnline = FALSE;
}


/***************************************************************************/
/* Function: command()                                                     */
/*                                                                         */
/* Check for valid \orx command; if invalid command, print error message    */
/***************************************************************************/

char * command(char *p)
{ p += 5;                                            /* skip over '{\orx' */
 if (*p++ != ' ') p = 0;
 if (p == 0) error("Unrecognised command string",7);
 return(p);
}


/***************************************************************************/
/* Function: error()                                                       */
/*                                                                         */
/* Print out error message, including string *s and 'n' characters         */
/* of inbuf.                                                               */
/***************************************************************************/

void error(char *s, int n)
{ char err_str[80]; int j;
 if (++err_cnt <= err_max)
   { if (n > 0)  { for (j=0; j<n; j++) err_str[j] = *(i_ptr+j);
                   err_str[j] = '\0';
                 }
     if (n == 0) { strcpy(err_str,"oct(");
                   chrcat(err_str,'0' + (*i_ptr/64));
                   chrcat(err_str,'0' + (*i_ptr/8));
                   chrcat(err_str,'0' + (*i_ptr & 7));
                   strcat(err_str,")");
                 }
     if (n < 0)  { err_str[0] = '\0'; }
   }
 printf("Line %4d    Error: %s %s\n",line_cnt,s,err_str);
}


/***************************************************************************/
/* Function: process()                                                     */
/*                                                                         */
/* Process input text within {\orx, converting to internal format in bnbuf */
/***************************************************************************/

#define ISAC(c) (((strchr("aAiIuUxXeEoO",c) != 0) && c) ? TRUE : FALSE)

/* wW removed from the definition of ISAC above (.R .L) */

void process(void)
{ int cap_flag, underscore;
unsigned char *i, c, d;
#define CF ac_flag=underscore=cap_flag=roman_flag=FALSE
#define CC CF; continue
#define CR ac_flag=underscore=cap_flag=FALSE;
#define CI i_ptr++; CC

CF;
while(1)
 { if (eof_flag) return;
   if (err_cnt >= err_max)
      { bnmode = FALSE; return; }
   c = *i_ptr; d = *(i_ptr+1);
/* END OF LINE */
   if ((c == '\0') || (c == '\n'))
     { bnword(); strcat (outbuf,i_ptr); write_outbuf(); getline(); CC; }
/* IMBEDDED ROMAN */
   if (strchr("!'()*+,-/:;=?[]`",c) || ((c == '.') && (*(i_ptr+1) == '.')))
   { if (c == '.') i_ptr++;
     if (bnbuf[0]) { bnword(); }
     while(1)
     { chrcat(outbuf,c); c = *++i_ptr;
       if (c == '.')
       { if (*(i_ptr+1) != '.') break;
         i_ptr++; continue;
       }
       if ((strchr("!'()*+,-/:;=?[]`",c) && c) == 0) break;
     }
     CR; continue;
   }
/* ILLEGAL CHARS */
   if (strchr("_$fqwxzBCDEFGJKNOPQSTVWXYZ\177",c))
      { error("Illegal oriya character: ",1); CI; }
   if (c>127) { error("Invalid character >80H: ",1); CI; }
/*?? Since we are now case sensitive (unlike skt), the list of */
/*?? illegal chars has been increased (_ added, and & removed) */
/* CONTROL CHARACTERS */
   if (c < ' ')
   { error("Illegal control character: ",0); CI; }
/* IMBEDDED LATEX COMMAND STRINGS */
   if (c == '\\')
   { if (d == '-')                 /* imbedded discretionary hyphen */
        { strcat(bnbuf,"!"); i_ptr++; CI; }
     bnword();
     if (isalpha(d) == 0)
        { chrcat(outbuf,c); chrcat(outbuf,*++i_ptr); CI; }
     else
     { while (1)
          { chrcat(outbuf,c); c = *++i_ptr; if (isalpha(c) == 0) break; }
     }
     CC;
   }
/* SPACE CHAR */
   if (c == ' ')
      { bnword(); while(*++i_ptr == ' '); chrcat(outbuf,c); CC;
      }
/*?? slight change here, since underscore is now an illegal character */
/* COMMENT DELIMITER */
   if (c == '%')
   { if (*(i_ptr+1) == '\n') bncont();
     else bnword();
     strcat(outbuf,i_ptr); write_outbuf(); getline(); CC;
   }

/* HASANTA */
   if (c == '&') {
       c = '@';
   }

/* BRACES */
   if (c == '{') { if (d == '}') { i_ptr++; CI; } /* for words like pra{}uga */
                   else { nest_cnt++; bncont(); chrcat(outbuf,c); CI; }
                 }
   if (c == '}')
      { bnword(); chrcat(outbuf,c);
        if (--nest_cnt == 0)
           { bnmode = FALSE;
             i_ptr++; return;
           }
        else CI;
      }
/* UPPER CASE */
   if (isupper(c))
          { switch (c)
                   { case 'A':
                     case 'I':
                     case 'U':
                     case 'M':
                     case 'H': break;
                     case 'L': break;
                     case 'R': c = 'w'; break;
                     default:  c = '*'; break;
                   }
            if (c=='*') { error("Invalid upper case: ",1); CI; }
          }
/*?? big change with that code: the upper case has a different *meaning* than */
/*?? the lower case: fortunately, AIUMH are the same as the internal code :-) */

/* EQUATE VA WITH BA */
   if (c == 'v') {
       c = 'b';
   }

/* DOT_CHAR */
   if (c == '.') { switch(d)
                         { case 'y': c = 'Y'; break;
                           case 'd': c = 'q'; break;
                           case 'h': c = 'H'; break;
                           case 'l': c = 'X'; break;
                           case 'm': c = 'M'; break;
                           case 'n': c = 'N'; break;
                           case 'o': c = '%'; break;
                           case 'r': c = 'x'; break;
                           case 's': c = 'S'; break;
                           case 't': c = 'f'; break;
                           case 'a': c = '\\'; break;
                           case 'b': c = 'v'; break;
                          }
                   if (c=='.') { error("Invalid dot_character: ",2); CI; }
                   i_ptr++; d = *(i_ptr+1);
                 }

/* NEXT CHAR IS H */
   if (d=='h')
      { if (strchr("bcdfgjkptqw",c)) { c=toupper(c); i_ptr++; d=*(i_ptr+1); }
      }

/* The upper/lowercase stuff removed: a following 'h' converts a consonant */
/* to its upper case internal code, e.g th --> T.  Note that 'w' is added  */
/* to the list for R Rh */

/* QUOTE CHAR */
   if (c == '\"') { switch(d)
                          { case 'n': c = 'z'; break;
                            case 's': c = 'Z'; break;
                          }
                    if (c=='\"') { error("Invalid quote_character",2); CI; }
                    i_ptr++; d = *(i_ptr+1);
                   }

/* TILDE CHAR */
   if (c == '~') { switch (d)
                   { case 'n': c = 'V'; break;
                     case 'm': c = '~'; break;
                     case 'r': c = 'R'; break;
                     default : c = '*'; break;
                   }
                   if (c=='*')
                      { error("Invalid use of tilde character: ",2); CI; }
                   i_ptr++; d = *(i_ptr+1);
                 }
/* TWO CHAR VOWELS */
   if ( strchr("aiu",c) && strchr("aiu",d) )
      { switch(c)
              { case 'a': switch(d)
                                { case 'a': c = 'A'; break;
                                  case 'i': c = 'E'; break;
                                  case 'u': c = 'O'; break;
                                } break;
                case 'i': if (d=='i') c = 'I'; break;
                case 'u': if (d=='u') c = 'U'; break;
              }
        if (isupper(c)) { i_ptr++; d = *(i_ptr+1); }
      }
/*?? all the upper/lowercase stuff removed */
/* NOW CHAR SHOULD BE INTERNAL REPRESENTATION OF SANSKRIT CHAR */
   if ( ((c=='~' || c=='M') && !(ac_flag)) ) {
      i_ptr -=2; error("No vowel before nasal: ",3); i_ptr +=2; CF;
   }

   if (c=='H' && !(ac_flag)) {
       i_ptr -=2; error("No vowel before visarga: ",3); i_ptr +=2; CF;
   }

   chrcat(bnbuf,c);
   CR;
   if (ISAC(c)) ac_flag = TRUE;
   i_ptr++;
  }
}

#undef CI
#undef CC
#undef CR
#undef CF


/***************************************************************************/
/* Function: chrcat()                                                      */
/*                                                                         */
/* Append character c to end of buffer s                                   */
/***************************************************************************/

void chrcat(char *s, char c)
{ char temp[] = " "; temp[0] = c; strcat(s,temp);
}


/***************************************************************************/
/* Function: bncont()                                                     */
/*                                                                         */
/* Similar to bnword() but used where input text line ends in '%' to      */
/* continue on next line.                                                  */
/***************************************************************************/

void bncont(void)
{
 cont_end = TRUE; bnword();
 cont_end = FALSE; cont_begin = TRUE;
}


/***************************************************************************/
/* Function: bnword()                                                     */
/*                                                                         */
/* Convert contents of bnbuf to output string in outbuf                   */
/***************************************************************************/

/* internal code for consonants */
static char hal_chars[] = "BCDFGJKLNPQRSTVWYZbcdfghjklmnpqrstvwyz";

#define ISHAL(c) (((strchr(hal_chars,c) != 0) && c) ? TRUE : FALSE)

#define CLRFLAGS ac_hook=post_ra=pre_ra=hasanta=hal_flag=post_ya=post_va=post_na=post_ma=post_la=0

#define CAT(w,x,z) \
strcat(w,x); strcat(w,z)

void bnword(void)
{ char c;
 if (roman_flag && bnbuf[0]) { strcat(outbuf,"\\,"); roman_flag = FALSE; }

/* A word is built up one syllable at a time: a syllable typically comprises  */
/* a consonant (or samyoga) followed by a vowel (with its nasalisation).      */
/* If there is no consonant, then a front-vowel is output; if there           */
/* is no vowel, then a viraama is appended to the consonant/samyoga.          */
/* One effect of this is that, if a consonant cluster is not fully resolved   */
/* into a single samyoga, it will be treated as two syllable: in particular,  */
/* the hook of the short-i will span one samyoga only.                        */
/*                                                                            */
/* The `work' buffer is used as a scratchpad while building a syllable; on    */
/* completion it is stored in the `tmp' buffer before shipping to the output  */
/* buffer. This temporary storage while working on the next syllable, allows  */
/* changes to the back spacing of the previous syllable for more effiecient   */
/* output.                                                                    */
/*                                                                            */
/* `ra' is difficult: the first `r' of a consonant cluster is simply flagged  */
/* in `pre_ra', and similarly the final `r' in `post_ra', and then these are  */
/* dealt with when appending a vowel.                                         */

 CLRFLAGS;
 s_ptr = bnbuf; c = *s_ptr;
 if (c == '\0') return;
 *tmp = '\0'; *work = '\0';
 while (1)
 {  CLRFLAGS; /* in particular, need to clear hal_flag for the likes of kara */
    c= *s_ptr++;
    if (c == '\0')
       { if (*tmp) { if (outbuf[0]=='\0' && tmp[0]=='[') strcat(outbuf,"{}");
                     strcat(outbuf,tmp);
                   }
         break;
       }
    if (ISAC(c))
       { ac_char = c;
         frontac();
         if (*tmp) { if (outbuf[0]=='\0' && tmp[0]=='[') strcat(outbuf,"{}");
                     strcat(outbuf,tmp);
                   }
         strcpy(tmp,work);
         *work = '\0'; cont_begin = 0;
         continue;
       }
    if (strchr("0123456789\"!%|\\@~HM",c))
       { single(c);
         if (*tmp) { if (outbuf[0]=='\0' && tmp[0]=='[') strcat(outbuf,"{}");
                     strcat(outbuf,tmp);
                   }
         strcpy(tmp,work);
         *work = '\0'; cont_begin = 0;
         continue;
       }
    if (c == 'r') { pre_ra = TRUE; c = *s_ptr; }
    else s_ptr--;
    old_sptr = s_ptr; /* save pointer to start of samyoga                    */
    if (ISHAL(c)) { hal_flag = TRUE; samyoga(); c = *s_ptr; }
    ac_char = hasanta = 0;
    if (!hr_flag) { if (ISAC(c)) { ac_char = *s_ptr++; }
                    else hasanta = TRUE;   /* hr_flag = h.r parsed by samyoga */
                  }
    backac(); hr_flag = FALSE;
    if (*tmp) { if (outbuf[0]=='\0' && tmp[0]=='[') strcat(outbuf,"{}");
                strcat(outbuf,tmp);
              }
    strcpy(tmp,work);
    *work = '\0'; cont_begin = FALSE;

 }
 strcat(outbuf,work);
 s_ptr = bnbuf; *s_ptr = '\0';
 cont_begin = 0;
}


/***************************************************************************/
/* Function: single()                                                      */
/*                                                                         */
/* Output single (stand-alone) character to work buffer                    */
/***************************************************************************/

void single(char c)
{
 switch(c)
 {  case '0':   strcat(work,"0");  break; /* numerals */
    case '1':   strcat(work,"1");  break;
    case '2':   strcat(work,"2");  break;
    case '3':   strcat(work,"3");  break;
    case '4':   strcat(work,"4");  break;
    case '5':   strcat(work,"5");  break;
    case '6':   strcat(work,"6");  break;
    case '7':   strcat(work,"7");  break;
    case '8':   strcat(work,"8");  break;
    case '9':   strcat(work,"9");  break;
    case '!':   strcat(tmp,"\\-"); break; /* discretionary hyphen */
    case '%':   strcat(work,"{\\char21}");  break;  /* OM */
    case '|':   strcat(work,".");  break; /* dnari */
    case '\\':   strcat(work,"{\\char253}"); break; /* avagraha */
    case '@':   strcat(work,"+");  break; /* hasanta */
    case '~':   strcat(work,"/");  break; /* candrabindu */
    case 'H':   strcat(work,"{\\char250}");  break; /* visarga */
    case 'M':   strcat(work,"M");  break; /* anusvara */

    }
}


/***************************************************************************/
/* Function: frontac()                                                     */
/*                                                                         */
/* Process a front-vowel to workbuf                                        */
/***************************************************************************/

void frontac(void)
{
 CLRFLAGS;
 switch(ac_char)
 {  case 'a': strcat(work,"a"); break;
    case 'A': strcat(work,"aA"); break;
    case 'i': strcat(work,"i"); break;
    case 'I': strcat(work,"I"); break;
    case 'u': strcat(work,"u"); break;
    case 'U': strcat(work,"U"); break;
    case 'x': strcat(work,"{\\char128}"); break;
    case 'X': strcat(work,"{\\char132}"); break;
    case 'e': strcat(work,"e"); break;
    case 'E': strcat(work,"E"); break;
    case 'o': strcat(work,"o"); break;
    case 'O': strcat(work,"O"); break;
    default : error("Lost in frontac()",-1);
 }
}


/***************************************************************************/
/* Function: sam_warning()                                                 */
/*                                                                         */
/* Print a warning message that a hasanta will be used within a             */
/* samyoga. Also print input file line number, together with an            */
/* indication of the samyoga and where the viraama will be placed.         */
/***************************************************************************/

void sam_warning(void)
{
 char *p, msg[80]="";
 p = old_sptr;
 if (pre_ra)
    { strcat(msg,"r");
      if (p==s_ptr) strcat(msg,"-");
    }
 while (ISHAL(*p))
 { switch (*p)
   { case 'B': strcat(msg,"bh");  break;
     case 'C': strcat(msg,"ch");  break;
     case 'D': strcat(msg,"dh");  break;
     case 'G': strcat(msg,"gh");  break;
     case 'H': strcat(msg,".h");  break;
     case 'J': strcat(msg,"jh");  break;
     case 'K': strcat(msg,"kh");  break;
     case 'P': strcat(msg,"ph");  break;
     case 'T': strcat(msg,"th");  break;
     case 'f': strcat(msg,".t");  break;
     case 'F': strcat(msg,".th"); break;
     case 'N': strcat(msg,".n");  break;
     case 'q': strcat(msg,".d");  break;
     case 'Q': strcat(msg,".dh"); break;
     case 'S': strcat(msg,".s");  break;
     case 'V': strcat(msg,"~n");  break;
     case 'Y': strcat(msg,".y");  break;
     case 'z': strcat(msg,"\"n"); break;
     case 'Z': strcat(msg,"\"s"); break;
     default:  chrcat(msg,*p);    break;
   }
   if (++p == s_ptr) strcat(msg,"-");
 }
 if (ISAC(*p))
    { switch (*p)
      { /* case 'w': strcat(msg,".l"); break; */
        case 'x': strcat(msg,".r"); break;
        default:  chrcat(msg,*p);   break;
      }
    }
 printf("Line %4d    Warning: samyoga viraama: %s\n",line_cnt,msg);
}


/***************************************************************************/
/* Function: backac()                                                      */
/*                                                                         */
/* Handle vowel diacritics                                                 */
/***************************************************************************/

void backac(void)
{  int j,k; char c, *p;

  if (pre_ra && (*work=='\0'))        /* case r.r, r.R, r.l, r.L, ru, rU, ra */
    { c = toupper(ac_char);
/*        if (c == 'X') {frontac(); } */

      if (c == 'X') {
        frontac();
        if ((ac_char == 'x') || (ac_char == 'X')) {
          strcat(work,"{\\char147}");
          ac_char = 'a';
        }
      } else {
        strcat(work,"r");
      }

      pre_ra = FALSE; hal_flag = TRUE;
    }

  if (post_ra) { strcat(work,"{\\char146}"); }      /* ra-phala */
  if (post_ya) { strcat(work,"{\\char138}"); }      /* ya-phala */
  if (post_va) { strcat(work,"{\\char164}"); }      /* va-phala */
  if (post_na) { strcat(work,"{\\char240}"); }      /* na-phala */
  if (post_ma) { strcat(work,"{\\char136}"); }      /* ma-phala */
  if (post_la) { strcat(work,"{\\char155}"); }      /* ma-phala */
  post_la = post_ma = post_na = post_va = post_ya = post_ra = 0;

  c = ac_char;

  if (pre_ra) { strcat(work,"{\\char147}"); }      /* add repha */
  if (hasanta) { strcat(work,"+"); }               /* add halanta */

  if (ac_char == 'A') { strcat(work,"A");}                    /* add aa-dia */
  if (ac_char == 'i') { strcat(work,"{\\char91}");}           /* add aa-dia */
  if (ac_char == 'I') { strcat(work,"X"); }                   /* add ii-dia */
  if (ac_char == 'u') {                                       /* add u-dia */
     if (low) {
          strcat(work,"{\\char216}");
          low = FALSE;
     } else {
          strcat(work,"{\\char93}");
     }
  }
  if (ac_char == 'U') { strcat(work,"{\\char90}");}           /* add uu-dia */
  if (ac_char == 'x') { strcat(work,"{\\char129}");}          /* add .r dia */
  if (ac_char == 'X') { strcat(work,"{\\char133}");}          /* add .l dia */
  if (ac_char == 'e') { CAT(tmp,"<",""); }                    /* add e-dia */
  if (ac_char == 'E') { CAT(tmp,"<",""); strcat(work,">");}   /* add ai-dia */
  if (ac_char == 'o') { CAT(tmp,"<",""); strcat(work,"A");}   /* add o-dia */
  if (ac_char == 'O') { CAT(tmp,"<",""); strcat(work,"*");}   /* add au-dia */

}

/***************************************************************************/
/* Function: samyoga()                                                     */
/*                                                                         */
/* Work along bnbuf sequentially to build up a samyoga print              */
/* string in the work buffer and update the samyoga parameters.            */
/*                                                                         */
/* The method is quite unsophisticated, but its simplicity lends           */
/* clarity for later additions or changes, and for this reason             */
/* is done in Devanagari alphabetical order, but with longer               */
/* strings before shorter.                                                 */
/*                                                                         */
/* Cr and Cy conjuncts are not defined in the individual cases for each    */
/* consonant. Rather these are handled in bulk by the program at the end   */
/* of the function.                                                        */
/*                                                                         */
/* Macros are used to simplify reading the program --- believe it or not!  */
/*                                                                         */
/* Switch/case is used on the first letter, then the main LS macro tests:  */
/*   (1) if the test string matches the input exactly, then                */
/*   (2) bump input pointer to the character after string match            */
/*   (3) use NC etc macro to break out of switch instruction               */
/***************************************************************************/


#define LS(a,c,z) n=strlen(a);        \
       if(strncmp(p,a,n)==0) { strcat(work,z); p+=n; c;}

#define NX sam_flag = 'X'; break;
#define NR sam_flag = 'R'; break;
#define NC sam_flag = 'C'; break;

#define IX p++; sam_flag = 'X'; break;
#define IR p++; sam_flag = 'R'; break;
#define IC p++; sam_flag = 'C'; break;

/******************************************************************************/

void samyoga(void)
{
char *p, sam_flag; int n;
sam_flag = 0;
p = s_ptr;
while (1)
{ if (!ISHAL(*p)) { NX; }
  switch (*p++)
  {


/* k */
case 'k': if(*p=='A')
                  {p+=1;  strcat(work,"{\\char157}"); hr_flag = TRUE; NR;}
          if(*p=='i')
                  {p+=1;  strcat(work,"{\\char141}"); hr_flag = TRUE; NR;}
          if(*p=='u')
                  {p+=1;  strcat(work,"{\\char139}"); hr_flag = TRUE; NR;}
          if(*p=='U')
                  {p+=1;  strcat(work,"{\\char150}"); hr_flag = TRUE; NR;}
          if(*p=='S' && *(p+1)=='N')
                  {p+=2;  strcat(work,"{\\char225}");NR;}
          LS("k",  NR, "{\\char199}" );
          LS("f",  NR, "{\\char27}" );
          LS("t",  NR, "{\\char182}" );
          LS("T",  NR, "k{\\char195}" );
          LS("r",  NR, "{\\char176}" );
          LS("s",  NR, "{\\char203}" );
          LS("S",  NR, "x" );
          strcat(work,"k"); NR;

/* kh */
case 'K': strcat(work,"K"); NR;

/* g */
case 'g': LS("g",  NR, "{\\char30}" );
          LS("D",  NR, "{\\char31}" );
          strcat(work,"g"); NR;

/* gh */
case 'G': strcat(work,"G"); NR;

/* "n */
case 'z': if(*p=='k' && *(p+1)=='S')
                  {p+=2;  strcat(work,"");NR;}
          LS("k",  NR, "{\\char148}");
          LS("K",  NR, "{\\char159}");
          LS("g",  NR, "{\\char160}");
          LS("G",  NR, "{\\char202}");
          LS("z",  NR, "{\\char10}");
          LS("t",  NR, "f{\\char174}");
          LS("D",  NR, "{\\char10}");
          strcat(work,"f"); NR;

/* c */
case 'c': if(*p=='A')
                  {p+=1;  strcat(work,"{\\char137}");  hr_flag = TRUE; NR;}
          LS("c",  NR, "{\\char152}");
          LS("C",  NR, "{\\char210}");
          strcat(work,"c"); NR;

/* ch */
case 'C': strcat(work,"C"); NR;

/* j */
case 'j': LS("j",  NR, "{\\char153}" );
          LS("J",  NR, "{\\char36}" );
          LS("V",  NR, "{\\char179}" );
          strcat(work,"j"); NR;

/* jh */
case 'J': strcat(work,"J"); NR;

/* ~n */
case 'V': LS("c",  NR, "{\\char172}" );
          LS("C",  NR, "{\\char230}" );
          LS("j",  NR, "{\\char189}" );
          LS("J",  NR, "{\\char173}" );
          strcat(work,"F"); NR;

/* .t */
case 'f': LS("k",  NR, "{\\char10}" );
          LS("f",  NR, "{\\char161}" );
          LS("F",  NR, "{\\char10}" );
          strcat(work,"q"); NR;

/* .th */
case 'F': strcat(work,"Q"); NR;

/* .da */
case 'q': if(*p=='i')
                  {p+=1;  strcat(work,"{\\char14}"); hr_flag = TRUE; NR;}
          LS("g",  NR, "{\\char127}" );
          LS("q",  NR, "{\\char236}" );
          strcat(work,"w"); NR;

/* .dh */
case 'Q': if(*p=='i')
                  {p+=1;  strcat(work,"{\\char15}"); hr_flag = TRUE; NR;}
          strcat(work,"W"); NR;

/* .n */
case 'N': LS("f",   NR, "{\\char242}" );
          LS("F",   NR, "{\\char220}" );
          LS("q",   NR, "{\\char187}" );
          LS("Q",   NR, "{\\char188}" );
          LS("N",   NR, "{\\char186}" );
          LS("t",   NR, "{\\char10}" );
       /* LS("m",   NR, "\\oSp{G}" ); */
          strcat(work,"N"); NR;

/* t */
case 't': if(*p=='A')
                  {p+=1;  strcat(work,"{\\char12}"); hr_flag = TRUE; NR;}
          if(*p=='i')
                  {p+=1;  strcat(work,"{\\char193}"); hr_flag = TRUE; NR;}
          if(*p=='u')
                  {p+=1;  strcat(work,"{\\char194}"); hr_flag = TRUE; NR;}
          if(*p=='U')
                  {p+=1;  strcat(work,"{\\char13}"); hr_flag = TRUE; NR;}
          LS("k",  NR, "k{\\char151}" );
          LS("t",  NR, "{\\char154}" );
          LS("T",  NR, "T{\\char151}" );
          LS("n",  NR, "{\\char162}" );
          LS("p",  NR, "{\\char28}" );
          LS("m",  NR, "{\\char163}" );
       /* LS("y",  NR, "y{\\char151}" ); */
          LS("r",  NR, "{\\char177}" );
          LS("b",  NR, "b{\\char151}" );
          LS("Z",  NR, "z{\\char151}" );
          LS("s",  NR, "{\\char241}" );
          strcat(work,"t"); NR;

/* th */
case 'T': strcat(work,"T"); NR;

/* d */
case 'd': if(*p=='i')
                  {p+=1;  strcat(work,"{\\char192}"); hr_flag = TRUE; NR;}
          LS("g",  NR, "{\\char234}");
          LS("G",  NR, "{\\char10}");
          LS("d",  NR, "{\\char169}");
          LS("D",  NR, "{\\char170}");
          LS("b",  NR, "{\\char10}");
          LS("B",  NR, "{\\char214}");
          strcat(work,"d"); NR;

/* dh */
case 'D': LS("y",  NR, "{\\char178}" );
          strcat(work,"D"); NR;

/* n */
case 'n': if(*p=='i')
                  {p+=1;  strcat(work,"{\\char22}"); hr_flag = TRUE; NR;}
          if(*p=='t' && *(p+1)=='r')
                   {p+=2; strcat(work,"{\\char232}");NR;}
          LS("f",  NR, "{\\char10}" );
          LS("q",  NR, "{\\char10}" );
          LS("t",  NR, "{\\char226}" );
          LS("T",  NR, "{\\char227}" );
          LS("d",  NR, "{\\char165}" );
          LS("D",  NR, "{\\char166}" );
          strcat(work,"n"); NR;

/* p */
case 'p': LS("t",  NR, "{\\char181}" );
          LS("p",  NR, "{\\char183}" );
          LS("s",  NR, "{\\char197}" );
          strcat(work,"p"); NR;

/* ph */
case 'P': strcat(work,"P"); NR;

/* b */
case 'b': LS("j",  NR, "{\\char34}" );
          LS("d",  NR, "{\\char167}" );
          LS("D",  NR, "{\\char29}" );
          LS("b",  NR, "{\\char168}" );
          strcat(work,"b"); NR;

/* bh */
case 'B': LS("N",  NR, "{\\char10}" );
          strcat(work,"B"); NR;

/* m */
case 'm': LS("N",  NR, "\\oSp{B}" );
       /* LS("n",  NR, "{\\char163}" ); */
          LS("p",  NR, "{\\char190}" );
          LS("P",  NR, "{\\char191}" );
          LS("B",  NR, "m{\\char239}" );
       /* LS("m",  NR, "{\\char215}" ); */
          strcat(work,"m"); NR;

/* y */
case 'y': strcat(work,"y"); NR;

/* .y */
case 'Y': strcat(work,"Y"); NR;

/* r */
case 'r': if(*p=='A')
                  {p+=1;  strcat(work,"{\\char11}"); hr_flag = TRUE; NR;}
          strcat(work,"r"); NR;

/* l */
case 'l': if(*p=='A')
                  {p+=1;  strcat(work,"{\\char158}"); hr_flag = TRUE; NR;}
          LS("k",  NR, "{\\char219}" );
          LS("g",  NR, "\\oSp{I}" );
          LS("f",  NR, "{\\char10}" );
          LS("q",  NR, "{\\char10}" );
          LS("p",  NR, "{\\char221}" );
          LS("P",  NR, "{\\char223}" );
          LS("l",  NR, "{\\char237}" );
          LS("Z",  NR, "{\\char10}" );
          LS("S",  NR, "{\\char10}" );
          strcat(work,"l"); NR;

/* v */
case 'v': LS("v",  NR, "{\\char168}" );
          strcat(work,"{\\char20}"); NR;

/* "s */
case 'Z': LS("c",  NR, "{\\char205}" );
          LS("C",  NR, "{\\char229}" );
          LS("f",  NR, "{\\char204}" );
          LS("Z",  NR, "{\\char10}" );
          strcat(work,"z"); NR;

/* .s */
case 'S': LS("k",  NR, "{\\char200}" );
          LS("f",  NR, "{\\char198}" );
          LS("F",  NR, "{\\char207}" );
          LS("N",  NR, "{\\char16}" );
          LS("p",  NR, "{\\char184}" );
          LS("P",  NR, "{\\char222}" );
          LS("S",  NR, "{\\char10}" );
          strcat(work,"S"); NR;

/* s */
case 's': if(*p=='t' && *(p+1)=='u')
                  {p+=2;  strcat(work,"{\\char32}"); hr_flag = TRUE; NR;}
          if(*p=='t' && *(p+1)=='r')
                  {p+=2;  strcat(work,"{\\char233}");NR;}
          LS("k",  NR, "{\\char201}" );
          LS("K",  NR, "{\\char26}" );
          LS("f",  NR, "{\\char10}" );
          LS("t",  NR, "{\\char180}" );
          LS("T",  NR, "{\\char228}" );
          LS("p",  NR, "{\\char185}" );
          LS("P",  NR, "{\\char224}" );
          LS("s",  NR, "\\oSp{H}" );
          strcat(work,"s"); NR;

/* h */
case 'h': if(*p=='i')
                  {p+=1;  strcat(work,"{\\char196}"); hr_flag = TRUE; NR;}
          if(*p=='u')
                  {p+=1;  strcat(work,"{\\char140}"); hr_flag = TRUE; NR;}
          /* if(*p=='x') { strcat(work,""); hr_flag = TRUE; IX; } */
          LS("N",  NR, "{\\char10}");
          LS("n",  NR, "{\\char94}");
          LS("b",  NR, "{\\char95}");
          strcat(work,"h"); NR;

/* R */
case 'w': LS("g",  NR, "{\\char126}");
          strcat(work,"w{\\char144}"); NR;

/* Rh */
case 'W': strcat(work,"W{\\char144}"); NR;

/* L */
case 'L': if(*p=='A')
                  {p+=1;  strcat(work,"{\\char208}"); hr_flag = TRUE; IX;}
          if(*p=='i')
                  {p+=1;  strcat(work,"{\\char149}"); hr_flag = TRUE; IX;}
          strcat(work,"L"); NR;

/* Assamese r */
case 'R': strcat(work,""); NR;

default: error("Lost in samyoga()",-1); NX;
}

  if (sam_flag == 'X') { s_ptr = p; break; }
  if (sam_flag == 'R') { if ((*p=='r')) { post_ra = TRUE; p++; }
                         if ((*p=='y')) { post_ya = TRUE; p++; }
                         if ((*p=='v' || *p=='b')) { post_va = TRUE; p++; }
                         if ((*p=='n')) { post_na = TRUE; p++; }
                         if ((*p=='m')) { post_ma = TRUE; p++; }
                         if ((*p=='l')) { post_la = TRUE; p++; }
                         s_ptr = p; break;
                       }
  if (!ISHAL(*p)) { s_ptr = p; break; }
 }
}

/***************************************************************************/
/*                                samapta                                  */
/***************************************************************************/