/*                                                               -*- C -*-
 *  CVT.C
 *
 *  (c)Copyright 1991-93 by Tobias Ferber, All Rights Reserved.
 */

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

static char rcs_id[]= "$VER: $Id: cvt.c 1.6 93/09/11 15:53:29 tf Exp $";

#include "cvt.h"


/*** / CVTOPEN / ***/

FILE *cvtopen(char *fname)
/* cvtopen() "offnet ein (level 3) Textfile zum lesen, wobei zun"achst
 * im aktuellen Verzeichnis nach 'fname' und 'fname.cvt' gesucht wird.
 * Wenn da nix ist, werden die durch PSEP getrennten Pfade aus der
 * Environment Variablen ENV_CVSCRIPTS davorgebaut und dort nachgesehen.
 * Der zur"uckgegebene FILE * kann mit fclose() geschlossen werden. */
{
  FILE *fp= NIL(FILE);

  if(fname)
  {
    char pname[MAXIMUM_PATHNAME_LENGTH];
    strcpy(pname,fname);

#ifdef DEBUG
    if(debuglevel >= 1)
      printf("> cvtopen() trying '%s'\n",pname);
#endif

    if(!(fp=fopen(pname,"r")))
    { 
      strcat(pname,CVT_EXT);

#ifdef DEBUG
      if(debuglevel >= 1)
        printf("                   '%s'\n",pname);
#endif

      if(!(fp=fopen(pname,"r")))
      { 
        char *ep= getenv(ENV_CVSCRIPTS);

        while(ep && *ep && !fp)
        {
          int i;

          for(i=0; *ep && (pname[i]= *ep++) != PSEP ; i++) ;

          if(i>0)
          { 
            if(pname[i-1]!='\\' && pname[i-1]!=':' && pname[i-1]!='/')
              pname[i++]= PSLASH;

            pname[i]= '\0';
            strcat(pname,fname);

#ifdef DEBUG
            if(debuglevel >= 1)
              printf("                   '%s'\n",pname);
#endif

            if(!(fp=fopen(pname,"r")))
            { strcat(pname,CVT_EXT);

#ifdef DEBUG
              if(debuglevel >= 1)
                printf("                   '%s'\n",pname);
#endif
              fp= fopen(pname,"r");
            }
          }
        }
      }
    }
  }
  return fp;
}


/*** / PARSEFILE / ***/

int parsefile(FILE *fp)
/* liest ein Scriptfile von 'fp' ein und baut die crules auf */
{
  crule_t *rp;

#ifdef DEBUG
  if(debuglevel >= 1)
    printf("Starting parse\n");
#endif

  while(!feof(fp) && global_numerrors < global_maxerrors)
  {
    if( rp= readcrule(fp) )
    {
      if( addcrule(rp) )
      { 
        lerror(rp->ln,"lhs is ambiguous!");

#ifdef DEBUG
        if(debuglevel >= 2)
          print_crule(rp);
#endif /* DEBUG */

      }
    }
  }

#ifdef DEBUG
  if(debuglevel >= 1)
    printf("> parsefile()  collected %ld rules,  %d errors\n"
           "Exiting parse\n", global_numrules, global_numerrors);
#endif

  readcrule( NIL(FILE) );

  return global_numerrors;
}


/*** / PUTRHS / ***/

void putrhs(crule_t *rp)
/* screibt das Pendant (rhs) zu der lhs von 'rp' nach 'fout' raus */
{ if(rp && rp->rhs)
  { int n;
    for(n=0; n<rp->r; n++)
      fputc((rp->rhs)[n],fout);
  }
  /* otherwise substitute lhs to nothing */
}


/*** / TAKEITEASY / ***/

void takeiteasy(void)
/* konvertiert 'fin' nach 'fout', falls global_maxlhs <= 1 ist */
{ while(!feof(fin))
  { char c=fgetc(fin);
    if(!feof(fin)) /* do NOT compare c!=EOF ! */
    { int r= (int)(c & 0xFF);
      if(crules[r]) putrhs(crules[r]);
      else fputc(c,fout);
    }
  }
}


/* convert scanner modes */
typedef enum { outer_mode,   /* reading chars */
               collect_mode, /* collecting chars for a match */
               look_mode,    /* looking for the first rule */
               match_mode,   /* trying to match buffered chars */
               flush_mode    /* flushing collected chars */
             } csmode_t;


/*** / DOTHEHARDPART / ***/

int dothehardpart(void)
{
  if(global_maxlhs<=1) takeiteasy(); /* optimize speed, better performance */
  else
  { char *cbuf, c;
    int l=0;  /* cbuf index */
    crule_t *ccr= NIL(crule_t); /* current crule */
    csmode_t smode= outer_mode;
    cbuf= malloc(global_maxlhs * sizeof(char));
    if(!cbuf)
    { echo("Ran out of memory to allocate %d bytes lhs buffer",
        global_maxlhs);
      return 1;
    }

    /* c==EOF is allowed! do NOT use the following shortcut
     * while((c=fgetc(fin))!=EOF &! feof(fin)) */

    while(!feof(fin))
    { c= fgetc(fin);
      if(!feof(fin)) do switch(smode)
      {
        case outer_mode:
         /* In diesem scanner mode wird das eingelesene Zeichen
          * + wieder rausgeschrieben, wenn keine Regeln daf"ur existieren.
          *   Der scanner bleibt dann im outer_mode.
          * + in sein Pendant "ubersetzt und dieses rausgeschrieben, wenn
          *   die Regel keinen rechten Kontext f"ur dieses Zeichen vorsieht.
          * + gepuffert und in den collect_mode "ubergegangen, wenn ein rechter
          *   Kontext "uberpr"uft werden mu\3.
          *   Hierbei wird der Kontext der obersten Regel angestrebt.
          */
#ifdef DEBUG
          if(debuglevel >= 4)
            printf("> outer_mode (c=$%x)\n",c&0xFF);
#endif
          { int r= (int)(c & 0xFF);
            if(ccr= crules[r])
            { if(ccr->l > 1) /* look ahead */
              { l=0;
                cbuf[l++]= c;
                smode= collect_mode;
              }
              else putrhs(ccr);
            }
            else fputc(c,fout);
          }
          break;

        case collect_mode:
         /* In diesem scanner mode wird "uberpr"uft, ob das eingelesene
          * Zeichen in den rechten Kontext der aktuellen Regel 'ccr' pa\3t
          * und in diesem Fall gepuffert.  Wenn damit der gesamte Kontext
          * erf"ullt ist, wird das Pendant rausgeschrieben und in den
          * outer_mode zur"uckgekehrt.
          * Pa\3t dieses Zeichen nicht zum Kontext der aktuellen Regel,
          * so wird in den match_mode "ubergegangen.
          */
#ifdef DEBUG
          if(debuglevel >= 4)
          { printf("> collect_mode (l=%d) for ",l);
            { int i; for(i=0; i<ccr->l; i++) putchar((ccr->lhs)[i]); }
            putchar('\n');
          }
#endif /* DEBUG */
          if((ccr->lhs)[l]==(cbuf[l]=c))
          { if(ccr->l==l+1) /* lhs matched */
            { putrhs(ccr);
              smode= outer_mode;
            }
            else ++l;
          }
          else { ++l; smode= match_mode; }
          break;

        case look_mode:
         /* In diesem scanner mode wird geschaut ob die gepufferten
          * Zeichen den rechten Kontext aus der ersten Regel f"ur das
          * oberste Pufferzeichen bilden.  Ist dies der Fall, so
          * werden die zum Kontext geh"orenden Zeichen aus dem Puffer
          * entfernt, deren Pendant ausgegeben und im look_mode
          * verblieben.  Sind noch nicht gen"ugend Zeichen gelesen,
          * so wird in den collect_mode, ansonsten in den match_mode
          * "ubergegangen.
          */
#ifdef DEBUG
          if(debuglevel >= 4)
            printf("> look_mode (l=%d)\n",l);
#endif
          if(l>0)
          { int r= (int)(cbuf[0] & 0xFF);
            if(ccr= crules[r])
            { int n, L; /* L := MIN(l,ccr->l) */
              L= (l <= ccr->l) ? l : ccr->l;
              for(n=0; n<L && (ccr->lhs)[n]==cbuf[n]; n++) ;
#ifdef DEBUG
              if(debuglevel >= 4)
              {
                printf("  L=%d, l=%d, ccr->l=%d, n=%d trying ",L,l,ccr->l,n);
                { int i; for(i=0; i<ccr->l; i++) putchar((ccr->lhs)[i]); }
                putchar('\n');
              }
#endif /* DEBUG */
              if(n == ccr->l) /* lhs matched */
              { putrhs(ccr);
                l-= ccr->l;
                if(l>0) /* some chars left */
                { int i;
                  for(i=0; i<=l; i++) cbuf[i]= cbuf[n+i];
                  smode= look_mode;
                }
                else smode= outer_mode; /* no chars left */
              }
              else if(n==l && l<=ccr->l) smode= collect_mode;
              else smode= match_mode;
            }
            else smode= flush_mode;
          }
          else smode= outer_mode;
          break;


        case match_mode:
         /* In diesem scanner mode wird versucht eine Regel aus der
          * Liste 'ccr' zu finden, die die gepufferten Zeichen abbaut.
          * Dabei wird der head 'ccr' selbst nicht mehr "uberpr"uft.
          * + Wenn es keine passende Regel gibt, so wird in den flush_mode
          *   "ubergegangen.
          * + Bleiben weitere Zeichen im Puffer "ubrig, so wird in
          *   den look_mode "ubergegangen, es sei denn es
          * + fehlen Zeichen zur "Uberpr"ufung eines Kontextes dann
          *   wird in den collect_mode "ubergegangen.
          * + Ist der Puffer leer, so wird in den outer_mode zur"uckgekehrt.
          */
#ifdef DEBUG
          if(debuglevel >= 4)
            printf("> match_mode (l=%d)\n",l);
#endif
          while((smode==match_mode) && (ccr=ccr->next))
          { int n, L;
            L= (l <= ccr->l) ? l : ccr->l;
            for(n=0; n<L && (ccr->lhs)[n]==cbuf[n]; n++) ;
#ifdef DEBUG
            if(debuglevel >= 4)
            {
              printf("  L=%d, l=%d, ccr->l=%d, n=%d trying ",L,l,ccr->l,n);
              { int i; for(i=0; i<ccr->l; i++) putchar((ccr->lhs)[i]); }
              putchar('\n');
            }
#endif /* DEBUG */
            if(n == ccr->l) /* lhs matched */
            { putrhs(ccr);
              l-= ccr->l;
              if(l>0) /* some chars left */
              { int i;
                for(i=0; i<=l; i++) cbuf[i]= cbuf[n+i];
                smode= look_mode;
              }
              else smode= outer_mode; /* no chars left */
            }
            else if(n==l && l<=ccr->l) smode= collect_mode;
          }
          if(smode==match_mode)
            smode= flush_mode; /* couln't match this one */
          break;

        case flush_mode:
         /* In diesem scanner mode wird das erste Zeichen des Puffers
          * aus diesem entfernt und im Klartext rausgeschrieben.
          * Wenn der Puffer dadurch leer wurde, wird in den outer_mode
          * "ubergegangen, ansonsten in den look_mode.
          */
#ifdef DEBUG
          if(debuglevel >= 4)
            printf("> flush_mode (l=%d)\n",l);
#endif
          fputc(cbuf[0],fout);
          if(--l>=0)
          { int i;
            for(i=0; i<=l; i++)
              cbuf[i]= cbuf[i+1];
            smode= look_mode;
          }
          else smode= outer_mode;
          break;

       } while((smode != outer_mode) && (smode != collect_mode));
     }
     /* EOF */
   }
   return 0;
}
