/***************************************************************************/
/*                                                                         */
/*  GRMK.C                                                                 */
/*                                                                         */
/*  Source code for "Gurmukhi for LaTeX" preprocessor.                     */
/*                                                                         */
/*  Based on Revision 1.1 1996/03/05 of skt.c preprocessor developed by    */
/*  Charles Wikner <wikner@nacdh4.nac.ac.za>                               */
/*                                                                         */
/*  Modifications to original source for Gurmukhi preprocessor made by     */
/*  Anshuman Pandey <apandey@u.washington.edu>, 1999/02/24                 */
/*                                                                         */
/***************************************************************************/

#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   sktcont     (void);
void   sktword     (void);
void   single      (char);
void   frontac     (void);
void   sam_warning (void);
void   backac      (void);
void   samyoga     (void);

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

#define TRUE 1
#define FALSE 0

unsigned char sktline;    /* flag TRUE if there is any sanskrit on this line */
unsigned char sktmode;    /* flag TRUE while within {\gm }                   */
unsigned char eof_flag;   /* flag True when end of file detected             */
unsigned char ac_flag;    /* flag TRUE while processing skt vowels           */
unsigned char roman_flag; /* flag TRUE if previous output was Roman string   */

int nest_cnt;            /* '{' increments, '}' decrements, while in \gm     */
int err_cnt;             /* incremented by any error while in \gm            */
#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 accent;     /* storage for working accent character             */
unsigned char ac_char;    /* storage for working vowel character              */
char sktbuf[255];         /* storage for sanskrit in internal code            */
char *s_ptr;              /* general pointer to sanskrit 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  virama;              /* flag to add viraama to samyoga (i.e. no vowel)   */
int  hr_flag;             /* flag indicates vowel picked up in samyoga (h.r)  */


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

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

  /* Initialization */

  sktmode = eof_flag = FALSE;
  nest_cnt = err_cnt = 0;
  line_cnt = 0;
  i_ptr = inbuf;  *i_ptr = '\0';
  s_ptr = sktbuf; *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("Gurmukhi for TeX, v1.0, 1999.03.02\n");
    printf("Anshuman Pandey <apandey@u.washington.edu>\n");
    printf("Syntax: grmk infile[.gm] [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,".gm");  /* 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(!sktmode && !eof_flag) search();  /* search for \gm command */
      while( sktmode && !eof_flag) process(); /* process bengali 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 \\gm = %d\n",nest_cnt); 
  fclose(infile);
  fclose(outfile);
  exit(1);

}


/***************************************************************************/
/* Function: search()                                                      */
/*                                                                         */
/* Search inbuf for '{\gm', 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 sktmode TRUE.                              */
/***************************************************************************/

void search(void)
{
unsigned char c;
char *p,*q;
  while (eof_flag == 0)
    { p = str_find(i_ptr,"{\\gm");
      if (p == 0)
        { if (sktline == 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 \gm */
        { p = i_ptr; i_ptr = q;         /* if bad \gm 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 '{\gm' */
      strcat(outbuf,i_ptr);             /* append partial line to outbuf */
      *p = c; i_ptr = p; 
      sktmode = TRUE; sktline = 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 (sktmode == FALSE) sktline = FALSE;
}


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

char * command(char *p)
{ p += 4;                                            /* skip over '{\gm' */
  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 {\gm, converting to internal format in sktbuf */
/***************************************************************************/

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

/* wWX removed from the definition of ISAC above (.R .l .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) 
       { sktmode = FALSE; return; }
    c = *i_ptr; d = *(i_ptr+1);

/* END OF LINE */
    if ((c == '\0') || (c == '\n'))
      { sktword(); strcat (outbuf,i_ptr); write_outbuf(); getline(); CC; }


/* IMBEDDED ROMAN */
/*    if (strchr("!'()*+,-/:;=?[]`",c) || ((c == '.') && (*(i_ptr+1) == '.')))
    { if (c == '.') i_ptr++;
      if (sktbuf[0]) { sktword(); }
      while(1) */

      if (strchr("!'()*+,-/:;=?[]`",c))
    { if (sktbuf[0]) { sktword(); }
      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("_$qwxBCDEFJLNOPQSVWXYZ\177",c)) 
       { error("Illegal Gurmukhi 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(sktbuf,"!"); i_ptr++; CI; }
      sktword(); 
      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 == ' ')
       { sktword(); 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') sktcont();
      else sktword();
      strcat(outbuf,i_ptr); write_outbuf(); getline(); CC;
    }

/* BRACES */
    if (c == '{') { if (d == '}') { i_ptr++; CI; } /* for words like pra{}uga */
                    else { nest_cnt++; sktcont(); chrcat(outbuf,c); CI; }
                  }
    if (c == '}')
       { sktword(); chrcat(outbuf,c);
         if (--nest_cnt == 0)
            { sktmode = FALSE; 
              i_ptr++; return; 
            }
         else CI;
       }

/* UPPER CASE */
    if (isupper(c)) 
           { switch (c)
                    { case 'A': 
                      case 'I':
                      case 'U':
                      case 'H': break;
                      case 'M': c = '\\'; break;
                      case 'K': c = 'L'; break;
                      case 'R': c = 'w'; break;
                      case 'G': 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 :-) */

/* DOT_CHAR */
    if (c == '.') { switch(d)
                          { case 'd': c = 'q'; break;
                            case 'h': c = 'H'; break;
                            case 'm': c = 'M'; break;
                            case 'n': c = 'N'; break;
                            case 'o': c = '%'; break;
                            case 't': c = 'x'; break;
                            case '.': c = '@'; break;
                            case ' ': c = '|'; break; /* following space */
                            case '\\': c = '|'; break; /* following LaTeX command */
                            case '}': c = '|'; break; /* following brace */
                            case '\0': c = '|'; break; /* end of line */
                            case '\n': c = '|'; break; /* end of line */
                          }
                  if (c=='.') { error("Invalid dot_character: ",2); CI; }
                  if (c!='|') { i_ptr++; d = *(i_ptr+1);}
                 }

/* NEXT CHAR IS H */
    if (d=='h')
       { if (strchr("bcdgjkptqx",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 = 'Y'; break;  
                             case 's': c = 'Z'; break;  
                           }
                     if (c=='\"') { error("Invalid quote_character",2); CI; }
                     i_ptr++; d = *(i_ptr+1);
                    } 
/*?? "d and "h removed */

/* TILDE CHAR */
    if (c == '~') { switch (d)
                    { case 'n': c = 'V'; 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(sktbuf,c);
    CR;
    if (ISAC(c)) ac_flag = TRUE;
    i_ptr++;
   }
}
/*?? all the tests for (semi-)vowel nasalization and accents removed */

#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: sktcont()                                                     */
/*                                                                         */
/* Similar to sktword() but used where input text line ends in '%' to      */
/* cotinue on next line.                                                   */
/***************************************************************************/

void sktcont(void)
{
  cont_end = TRUE; sktword();
  cont_end = FALSE; cont_begin = TRUE;
}


/***************************************************************************/
/* Function: sktword()                                                     */
/*                                                                         */
/* Convert contents of sktbuf to output string in outbuf                   */
/***************************************************************************/

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

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

#define CLRFLAGS virama=hal_flag=0

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

void sktword(void)
{ char c;
  if (roman_flag && sktbuf[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 and    */
/* accents). 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.                                                                    */

  CLRFLAGS;
  s_ptr = sktbuf; 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;
        }
     s_ptr--;
     old_sptr = s_ptr; /* save pointer to start of samyoga                    */
     if (ISHAL(c)) { hal_flag = TRUE; samyoga(); c = *s_ptr; }
     ac_char = virama = 0; 
     if (!hr_flag) { if (ISAC(c)) { ac_char = *s_ptr++; }
                     else virama = 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 = sktbuf; *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,"{\\char35}"); break;  /* pra.nava */
     case '|':   strcat(work,".");  break;  /* single danda */
     case '@':   strcat(work,"|");  break;  /* double danda */
     case '\\':  strcat(work,"{\\kern-1.8pt:}"); break;  /* candrabindu */
     case 'H':   strcat(work,"{\\char92}"); break;  /* visarga */
     case 'M':   strcat(work,"{\\tpp}"); 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,"ie"); break;
     case 'I': strcat(work,"eI"); break;
     case 'u': strcat(work,"uU"); break;
     case 'U': strcat(work,"u<"); break;
     case 'e': strcat(work,"eE"); break;
     case 'E': strcat(work,"a>"); break;
     case 'o': strcat(work,"o");  break;
     case 'O': strcat(work,"aO"); break;
     default : error("Lost in frontac()",-1);
  }
}


/***************************************************************************/
/* Function: sam_warning()                                                 */
/*                                                                         */
/* Print a warning message that a virama 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;
  
  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 'L': strcat(msg,"K");   break;
      case 'P': strcat(msg,"ph");  break;
      case 'T': strcat(msg,"th");  break;
      case 'x': strcat(msg,".t");  break;
      case 'X': strcat(msg,".th"); break;
      case 'N': strcat(msg,".n");  break;
      case 'q': strcat(msg,".d");  break;
      case 'Q': strcat(msg,".dh"); break;
      case 'f': strcat(msg,"f");   break;
      case 'V': strcat(msg,"~n");  break;
      case 'w': strcat(msg,"R");   break;
      case 'W': strcat(msg,"G");   break;
      case 'z': strcat(msg,"z");   break;
      case 'Y': strcat(msg,"\"n"); break;
      case 'Z': strcat(msg,"\"s"); break;
      case 'r': strcat(msg,"r");   break;
      default:  chrcat(msg,*p);    break;
    }
    if (++p == s_ptr) strcat(msg,"-");
  }
  if (ISAC(*p))
     { switch (*p)
       { /* case 'w': strcat(msg,".l"); break; */
         default:  chrcat(msg,*p);   break;
       }
     }
  printf("Line %4d    Warning: samyoga viraama: %s\n",line_cnt,msg);
}         

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

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

c = ac_char;

if (ac_char == 'A') { strcat(work,"A");}          /* add aa-dia */
if (ac_char == 'i') { CAT(tmp,"i",""); }          /* add i-dia */
if (ac_char == 'I') { strcat(work,"I"); }           /* add ii-dia */
if (ac_char == 'u') { strcat(work,"U");}            /* add u-dia */ 
if (ac_char == 'U') { strcat(work,"<");}            /* add uu-dia */
if (ac_char == 'e') { strcat(work,"E"); }           /* add e-dia */
if (ac_char == 'E') { strcat(work,">"); }           /* add ai-dia */
if (ac_char == 'o') { strcat(work,"{\\char126}");}  /* add o-dia */
if (ac_char == 'O') { strcat(work,"O");}            /* add au-dia */

/* if (virama)         { strcat(work,"\\30Cz"); }    /* add virama */

}

/***************************************************************************/
/* Function: samyoga()                                                     */
/*                                                                         */
/* Work along sktbuf 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.                                                 */  
/*                                                                         */
/* 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 NX 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; 

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

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': LS("k",  NX, "{\\adk}c"); 
           LS("K",  NX, "{\\adk}K"); 
           LS("r",  NX, "cq");
           strcat(work,"c"); break;
           
 /* kh */
 case 'K': LS("y",  NX, "kw");
           strcat(work,"k"); break;

 /* g */          
 case 'g': LS("g",  NX, "{\\adk}g");
           LS("G",  NX, "{\\adk}G");
           LS("r",  NX, "gq");
           strcat(work,"g"); break;
           
 /* gh */
 case 'G': strcat(work,"G"); break;
           
 /* "n */
 case 'Y': if(*p=='g' && *(p+1)=='i')
                  {p+=2;  strcat(work,"{\\tpp}ig");NX;}
           LS("k",  NX, "{\\tpp}c"); 
           LS("K",  NX, "{\\tpp}k"); 
           LS("g",  NX, "{\\tpp}g");
           LS("G",  NX, "{\\tpp}G"); 
           strcat(work,"L"); break;
           
 /* c */
 case 'c': LS("c",  NX, "{\\adk}C");
           LS("C",  NX, "{\\adk}x");
           strcat(work,"C"); break;
           
 /* ch */
 case 'C': strcat(work,"x"); break;
           
 /* j */
 case 'j': LS("j",  NX, "{\\adk}j");
           LS("J",  NX, "{\\adk}J");
           strcat(work,"j"); break;
           
 /* jh */
 case 'J': strcat(work,"J"); break;

 /* ~n */
 case 'V': LS("c",  NX, "{\\tpp}C"); 
           LS("C",  NX, "{\\tpp}x"); 
           LS("j",  NX, "{\\tpp}j"); 
           LS("J",  NX, "{\\tpp}J"); 
           strcat(work,"M"); break;

 /* .t */
 case 'x': LS("x",  NX, "{\\adk}t");
           LS("X",  NX, "{\\adk}T");
           strcat(work,"t"); break;

 /* .th */
 case 'X': strcat(work,"T"); break;
           
 /* .da */
 case 'q': LS("q",  NX, "{\\adk}D");
           LS("Q",  NX, "{\\adk}Q");
           strcat(work,"D"); break;
           
 /* .dh */
 case 'Q': strcat(work,"Q"); break;
           
 /* .n */
 case 'N': LS("x",  NX, "{\\tpp}t"); 
           LS("X",  NX, "{\\tpp}T"); 
           LS("q",  NX, "{\\tpp}D"); 
           LS("Q",  NX, "{\\tpp}Q"); 
           strcat(work,"N"); break;
 
 /* t */
 case 't': LS("t",  NX, "{\\adk}V");
           LS("T",  NX, "{\\adk}W");
           LS("r",  NX, "Vq");
           strcat(work,"V"); break;
           
 /* th */
 case 'T': strcat(work,"W"); break;

 /* d */
 case 'd': LS("d",  NX, "{\\adk}d"); 
           LS("D",  NX, "{\\adk}Y");
           LS("y",  NX, "dw");
           LS("r",  NX, "dq");
           LS("v",  NX, "dX");
           strcat(work,"d"); break;
           
 /* dh */
 case 'D': strcat(work,"Y"); break;
           
 /* n */
 case 'n': if(*p=='n' && *(p+1)=='i')
                 {p+=2;  strcat(work,"i{\\tpt}n");NX;}
           LS("t",  NX, "{\\tpp}V"); 
           LS("T",  NX, "{\\tpp}W"); 
           LS("d",  NX, "{\\tpp}d"); 
           LS("D",  NX, "{\\tpp}Y"); 
           LS("n",  NX, "{\\tpp}n");
           LS("h",  NX, "nH");
           strcat(work,"n"); break;

 /* p */
 case 'p': LS("p",  NX, "{\\adk}p");
           LS("P",  NX, "{\\adk}f");
           LS("r",  NX, "pq");
           strcat(work,"p"); break;
           
 /* ph */
 case 'P': strcat(work,"f"); break;
           
 /* b */
 case 'b': LS("b",  NX, "{\\adk}b");
           LS("B",  NX, "{\\adk}B");
           LS("r",  NX, "bq");
           strcat(work,"b"); break;

 /* bh */
 case 'B': strcat(work,"B"); break;
           
 /* m */
 case 'm': if(*p=='m' && *(p+1)=='i')
                  {p+=2;  strcat(work,"i{\\tpt}m");NX;}
           LS("p",  NX, "{\\tpp}p");
           LS("P",  NX, "{\\tpp}f");
           LS("b",  NX, "{\\tpp}b");
           LS("B",  NX, "{\\tpp}B");
           LS("m",  NX, "{\\tpp}m");
           LS("r",  NX, "mq");
           strcat(work,"m"); break;

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

 /* r */
 case 'r': LS("h",  NX, "rH");
           strcat(work,"r"); break;

 /* l */
 case 'l': LS("l",  NX, "{\\adk}l");
           LS("h",  NX, "lH");
           strcat(work,"l"); break;
           
 /* v */
 case 'v': LS("h",  NX, "vH");
           strcat(work,"v"); break;

 /* "s */
 case 'Z': strcat(work,"S"); break;
           
 /* s */           
 case 's': LS("s",  NX, "{\\adk}s");
           LS("v",  NX, "sX");
           strcat(work,"s"); break;
           
 /* h */           
 case 'h': strcat(work,"h"); break;

 /* K */
 case 'L': strcat(work,"K"); break;

 /* G */
 case 'W': strcat(work,"Z"); break;

 /* z */
 case 'z': strcat(work,"z"); break;

 /* R */
 case 'w': LS("h",  NX, "RH");
           strcat(work,"R"); break;

 /* f */
 case 'f': strcat(work,"F"); break;
 
 default: error("Lost in samyoga()",-1); NX;
 }

   if (sam_flag == 'X') { s_ptr = p; break; }
   if (!ISHAL(*p)) { s_ptr = p; break; }
  }  
}

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