/*
$VER: reader:c 1.0 (13-Sep-95) Copyright © by Lars Düning
*/

/*---------------------------------------------------------------------------
** Reading of C-Sources and filter for #include statements.
** Also here are routines for the modification of Makefiles.
**
** Copyright © 1995  Lars Düning  -  All rights reserved.
** Permission granted for non-commercial use.
**---------------------------------------------------------------------------
** Credits:
**   The buffering scheme was inspired by the input module of lcc (by
**   Chris Fraser & David Hanson).
**---------------------------------------------------------------------------
** C: DICE 3.01
**---------------------------------------------------------------------------
** [lars] Lars Düning; Am Wendenwehr 25; D-38114-Braunschweig;
**                     Germany; Tel. 49-531-345692
**---------------------------------------------------------------------------
** 10-Sep-95 [lars] v 1.0
** 13-Sep-95 [lars] current
**---------------------------------------------------------------------------
*/

#include <assert.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include "reader.h"

/*-------------------------------------------------------------------------*/

#define BUFSIZE 10240              /* Size of the file buffer */
static char   aBuffer[BUFSIZE+1];  /* File buffer */
static int    fdFile;              /* Filehandle */
static int    fdFile2;             /* Filehandle Writer */
static char * pLimit;              /* Pointer to closing sentinel in buffer */
static char * pch;                 /* Pointer to actual char in buffer */
static char * pIName;              /* Start of include filename */

#define END_OF_BUF()  (pch >= pLimit)
#define END_OF_FILE() (pLimit == aBuffer)

/*-------------------------------------------------------------------------*/
void
reader_init (void)

/* Initialize the reader.
 */

{
  fdFile = -1;
  fdFile2 = -1;
  aBuffer[BUFSIZE] = '\n';
  pLimit = aBuffer+BUFSIZE;
  pch = pLimit;
  pIName = NULL;
}

/*-------------------------------------------------------------------------*/
static int
fillbuffer (void)

/* Read in the next chunk from file such that incrementing pch will
 * access the newly read data.
 *
 * Result:
 *   0 on success, non-0 else (errno holds error code then).
 *   pch is set to the start of the new data.
 */

{
  int iRead;

  if (END_OF_FILE())
    return 1;
  if (pIName)
  {
    if (pch > pLimit)
      pch = pLimit;
    memcpy(aBuffer, pIName, pLimit-pIName);
    pch -= (pLimit-pIName);
  }
  else
    pch = aBuffer;
  iRead = read(fdFile, pch, aBuffer+BUFSIZE-pch);
  if (iRead >= 0)
  {
    pLimit = pch+iRead;
    *pLimit = '\n';
  }
  return iRead < 0;
}

/*-------------------------------------------------------------------------*/
int
reader_open (const char *pName)

/* Open a file for reading.
 *
 *   pName: Name of the file to read.
 *
 * Result:
 *   0 on success, non-0 else (errno contains error code then).
 *
 * The reader is initialized to read and filter the file name *pName.
 */

{
  assert(pName);
  assert(fdFile < 0);
  reader_init();
  fdFile = open(pName, O_RDONLY);
  return fdFile >= 0;
}

/*-------------------------------------------------------------------------*/
int
reader_openrw (const char *pNameR, const char *pNameW)

/* Open two makefiles for modification.
 *
 *   pNameR: Name of the file to read, may be NULL.
 *   pNameW: Name of the file to read.
 *
 * Result:
 *   0 on success, non-0 else (errno contains error code then).
 *
 * The reader is initialized to read the old makefile *pNameR and writing
 * of new makefile *pNameW.
 */

{
  assert(pNameW);
  assert(fdFile < 0);
  assert(fdFile2 < 0);
  reader_init();
  fdFile2 = open(pNameW, O_WRONLY|O_CREAT|O_TRUNC);
  if (fdFile2 < 0)
    return 1;
  if (pNameR)
  {
    fdFile = open(pNameR, O_RDONLY);
    if (fdFile < 0)
    {
      close(fdFile2);
      fdFile2 = -1;
    }
  }
  return fdFile2 < 0;
}

/*-------------------------------------------------------------------------*/
int
reader_close (void)

/* Close the file currently read.
 * Result:
 *   0 on success, non-0 else.
 *   errno set to error code in case.
 */

{
  int i;

  i = 0;
  if (fdFile >= 0)
  {
    i = close(fdFile);
  }
  fdFile = -1;
  if (fdFile2 >= 0)
  {
    i = close(fdFile2) || i;
  }
  fdFile = -1;
  return i;
}

/*-------------------------------------------------------------------------*/
int
reader_eof (void)

/* Test if the file read is at its end.
 *
 * Result:
 *   Non-0 on end of file.
 */

{
  return END_OF_FILE();
}

/*-------------------------------------------------------------------------*/
const char *
reader_get (void)

/* Extract the next include statement from the file being read.
 *
 * Result:
 *   Pointer to the filename extracted from the statement, or NULL on error
 *     or end of file.
 *     The memory is property of reader_get(), the pointer valid just until
 *     the next call to reader_get().
 *   errno set to an error code in case.
 */

{
  enum { StartOfLine, InLine, FoundHash, SkipComment, SkipLComment, FoundInclude
      }  eState;
  char   *stab[]
    = {
        "Start "
      , "InLine"
      , "Found#"
      , "SkipC "
      , "SkipLC"
      , "FoundI"
      };
  char   ch;
  int    iFound; /* Number of chars found of "include" */
  int    bDone;

#define GETCHAR() \
  ch = *pch++; \
  if (ch == '\n' && pch >= pLimit) \
  { \
    if (fillbuffer()) { \
      pIName = NULL; \
      return NULL; \
    } \
    ch = *pch++; \
  }

  if (pIName)
    eState = InLine;
  else
    eState = StartOfLine;
  pIName = NULL;

  bDone = 0;
  while (!bDone)
  {
    /* Get the next interesting non-linefeed character */
    do {
      GETCHAR()
      if (ch == '\n' || ch == '\r' || ch == '\v' || ch == '\f')
      {
        eState = StartOfLine;
        pIName = NULL;
      }
      else
        break;
    } while (1);

    switch(eState)
    {
    case StartOfLine:
      switch (ch)
      {
      case '#':
        eState = FoundHash;
        iFound = 0;
        break;
      case '/':
        GETCHAR()
        if (ch == '/')
          eState = SkipLComment;
        else if (ch == '*')
          eState = SkipComment;
        else
          eState = InLine;
        break;
      case ' ': case '\t':
        /* still: eState = StartOfLine; */
        break;
      default:
        eState = InLine;
        break;
      }
      break;

    case InLine:
      switch (ch)
      {
      case '/':
        GETCHAR()
        if (ch == '/')
          eState = SkipLComment;
        else if (ch == '*')
          eState = SkipComment;
        else
          eState = InLine;
        break;
      case '\\':
        GETCHAR()
        /* still: eState = InLine; */
        break;
      default:
        /* still: eState = InLine; */
        break;
      }
      break;

    case FoundHash:
      switch(ch)
      {
      case ' ': case '\t':
        if (iFound)
        {
          eState = InLine;
          iFound = 0;
        }
        /* else still: eState = FoundHash */
        break;
      case 'i': case 'n': case 'c': case 'l':
      case 'u': case 'd': case 'e':
        if ("include"[iFound] != ch)
        {
          eState = InLine;
          iFound = 0;
        }
        else if (ch == 'e')
        {
          eState = FoundInclude;
          pIName = NULL;
        }
        else
          iFound++;
        break;
      default:
        eState = InLine;
      }
      break;

    case FoundInclude:
      switch(ch)
      {
      case ' ': case '\t':
        /* still: eState = FoundInclude */
        break;
      case '<':
        if (!pIName)
        {
          eState = InLine;
        }
        break;
      case '"':
        if (!pIName)
        {
          pIName = pch;
        }
        else
        {
          *(pch-1) = '\0';
          if (pch-1 == pIName)
            pIName = NULL;
          else
            bDone = 1;
          eState = InLine;
        }
        break;

      default:
        if (!pIName)
          eState = InLine;
        break;
      }
      break;

    } /* switch (eState) */

    /* Skip comments immediately */
    if (eState == SkipComment)
    {
      while(1) {
        do {
          GETCHAR()
        } while (ch != '*');
        GETCHAR()
        if (ch == '/')
        break;
      }
      eState = StartOfLine;
    }
    else if (eState == SkipLComment)
    {
      do {
        GETCHAR()
      } while (ch != '\n' && ch != '\r' && ch != '\v' && ch != '\f');
      eState = StartOfLine;
    }
  } /* while(!bDone) */
  return pIName;

#undef GETCHAR
}

/*-------------------------------------------------------------------------*/
int
reader_writeflush (void)

/* Write out the buffer to the written makefile.
 * Return 0 on success, non-0 else (errno holds the error code then).
 */

{
  if (pch > aBuffer && write(fdFile2, aBuffer, pch-aBuffer) != pch-aBuffer)
    return 1;
  pch = aBuffer;
  return 0;
}

/*-------------------------------------------------------------------------*/
int
reader_writen (const char *pText, int len)

/* Append the first <len> characters of <pText> to the written file.
 * Return 0 on success, non-0 else (errno holds the error code then)
 */

{
  assert(pText);
  assert(fdFile2 >= 0);
  if (pLimit-pch < len && reader_writeflush())
    return 1;
  memcpy(pch, pText, len);
  pch += len;
  return 0;
}

/*-------------------------------------------------------------------------*/
int
reader_write (const char *pText)

/* Append <pText> to the written file.
 * Return 0 on success, non-0 else (errno holds the error code then)
 */

{
  return reader_writen(pText, strlen(pText));
}

/*-------------------------------------------------------------------------*/
int
reader_copymake (const char * pTagline)

/* Copy the file from fdFile to fdFile2 up to including the tagline.
 * If the tagline is not found, it is appended.
 * The tagline MUST end in a newline character!
 * Return 0 on success, non-0 on error (errno holds the error code then).
 */

{
  int len;
  int count; /* Next charactor to compare in pTagline, or -1 if to skip
              * up to the next Newline */
  char ch;


  assert(pTagline);
  assert(fdFile2);
  len = strlen(pTagline);
  assert(len < 120);

  /* Simplest case: no old makefile to copy */
  if (fdFile < 0)
  {
    strcpy(aBuffer, pTagline);
    pch = aBuffer+len;
    return 0;
  }
  if (fillbuffer())
    return 1;
  count = 0;

  while (!END_OF_FILE() && count < len)
  {
    if (END_OF_BUF())
    {
      if (write(fdFile2, aBuffer, pch-aBuffer) != pch-aBuffer)
        return 1;
      if (fillbuffer())
        return 1;
      if (END_OF_FILE())
        break;
    }
    ch = *pch;
    if (count < 0)
    {
      if (ch == '\n')
        count = 0;
    }
    else
    {
      if (ch == pTagline[count])
        count++;
      else if (ch == '\n')
        count = 0;
      else
        count = -1;
    }
    pch++;
  }

  /* Switch to write mode */
  pLimit = aBuffer+BUFSIZE;

  /* Append a newline if missing */
  if (count < 0 && reader_writen("\n", 1))
    return 1;

  /* Put the Tagline into the buffer if necessary */
  if (count < len && reader_writen(pTagline, len))
    return 1;

  return 0;
}

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