/**********************************************************************
 * pas.c
 * Main process
 *
 *   Copyright (C) 2008-2009 Gregory Nutt. All rights reserved.
 *   Author: Gregory Nutt <spudmonkey@racsa.co.cr>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 **********************************************************************/

/**********************************************************************
 * Included Files
 **********************************************************************/

#define _GNU_SOURCE
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <signal.h>
#include <errno.h>

#include "config.h"
#include "keywords.h"
#include "pasdefs.h"
#include "ptdefs.h"
#include "podefs.h"
#include "pedefs.h"

#include "pas.h"
#include "paslib.h"   /* For extension */
#include "pproc.h"    /* For primeBuiltInProcedures */
#include "pfunc.h"    /* For primeBuiltInFunctions */
#include "ptkn.h"     /* For primeTokenizer */
#include "ptbl.h"     /* For primeSymbolTable */
#include "pofflib.h"  /* For poffInitializeForOutput() */
#include "poff.h"     /* For POFF definitions */
#include "pprgm.h"    /* for program() */
#include "punit.h"    /* for unit() */
#include "perr.h"     /* for error() */

/**********************************************************************
 * Pre-processor Definitions
 **********************************************************************/

/**********************************************************************
 * Global Variables
 **********************************************************************/

/* Unitialized Global Data */

uint16_t    token;                   /* Current token */
uint16_t    tknSubType;              /* Extended token type */
int32_t     tknInt;                  /* Integer token value */
double      tknReal;                 /* Real token value */
STYPE      *tknPtr;                  /* Pointer to symbol token*/
WTYPE       withRecord;              /* RECORD used with WITH statement */
FTYPE       files[MAX_FILES+1];      /* File Table */
fileState_t fileState[MAX_INCL];     /* State of all open files */

/* sourceFileName : Program name from command line
 * includePath[] : Pathes to search when including file
 */

char       *sourceFileName;
char       *includePath[MAX_INCPATHES];

poffHandle_t poffHandle;             /* Handle for POFF object */

FILE       *poffFile;                /* Pass1 POFF output file */
FILE       *lstFile;                 /* List File pointer */
FILE       *errFile;                 /* Error file pointer */

/* Initialized Global Data */

int16_t     level        = 0;        /* Static nesting level */
int16_t     includeIndex = 0;        /* Include file index */
int16_t     nIncPathes   = 0;        /* Number pathes in includePath[] */
uint16_t    label        = 0;        /* Last label number */
int16_t     nsym         = 0;        /* Number symbol table entries */
int16_t     nconst       = 0;        /* Number constant table entries */
int16_t     sym_strt     = 0;        /* Symbol search start index */
int16_t     const_strt   = 0;        /* Constant search start index */
int16_t     err_count    = 0;        /* Error counter */
int16_t     nfiles       = 0;        /* Program file counter */
int32_t     warn_count   = 0;        /* Warning counter */
int32_t     dstack       = 0;        /* data stack size */

/**********************************************************************
 * Private Type Definitions
 **********************************************************************/

struct outFileDesc_s
{
  const char *extension;
  const char *flags;
  FILE      **stream;
};
typedef struct outFileDesc_s outFileDesc_t;

/**********************************************************************
 * Private Variables
 **********************************************************************/

static const outFileDesc_t outFiles[] =
{
  { "o1",  "wb", &poffFile },      /* Pass 1 POFF object file */
#if LSTTOFILE
  { "lst", "w",  &lstFile },       /* List file */
#endif
  { "err", "w",  &errFile },       /* Error file */
  { NULL,  NULL }                  /* (terminates list */
};

static const char *programName;

/***********************************************************************
 * Private Function Prototypes
 ***********************************************************************/

static void closeFiles(void);
static void openOutputFiles(void);
static void showUsage(void);
static void parseArguments(int argc, char **argv);

/***********************************************************************
 * Private Functions
 ***********************************************************************/

static void closeFiles(void)
{
  const outFileDesc_t *outFile;

  /* Close input source files */

  for(; includeIndex >= 0; includeIndex--)
    {
      if (FP->stream)
        {
          (void)fclose(FP->stream);
          FP->stream = NULL;
        }
    }

  /* Close output files */

  for (outFile = outFiles; outFile->extension; outFile++)
    {
      if (*outFile->stream)
        {
          (void)fclose(*outFile->stream);
          *outFile->stream = NULL;
        }
    }
}

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

static void openOutputFiles(void)
{
  const outFileDesc_t *outFile;
  char tmpname[FNAME_SIZE+1];

  /* Open output files */

  for (outFile = outFiles; outFile->extension; outFile++)
    {
      /* Generate an output file name from the source file
       * name and an extension associated with the output file.
       */

      (void)extension(sourceFileName, outFile->extension, tmpname, 1);
      *outFile->stream = fopen(tmpname, outFile->flags);
      if (*outFile->stream == NULL)
        {
          fprintf(stderr, "Could not open output file '%s': %s\n",
                  tmpname, strerror(errno));
          showUsage(); 
        }
    }
}

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

static void signalHandler(int signo)
{
#ifdef  _GNU_SOURCE
  fprintf(errFile, "Received signal: %s\n", strsignal(signo));
  fprintf(lstFile, "Received signal: %s\n", strsignal(signo));
#else
  fprintf(errFile, "Received signal %d\n", signo);
  fprintf(lstFile, "Received signal %d\n", signo);
#endif
  closeFiles();
  error(eRCVDSIGNAL);
  exit(1);
}

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

static void primeSignalHandlers(void)
{
  (void)signal(SIGHUP,  signalHandler);
  (void)signal(SIGINT,  signalHandler);
  (void)signal(SIGQUIT, signalHandler);
  (void)signal(SIGILL,  signalHandler);
  (void)signal(SIGABRT, signalHandler);
  (void)signal(SIGSEGV, signalHandler);
  (void)signal(SIGTERM, signalHandler);
}

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

static void showUsage(void)
{
  fprintf(stderr, "USAGE:\n");
  fprintf(stderr, "  %s [options] <filename>\n", programName);
  fprintf(stderr, "[options]\n");
  fprintf(stderr, "  -I<include-path>\n");
  fprintf(stderr, "    Search in <include-path> for additional file\n");
  fprintf(stderr, "    A maximum of %d pathes may be specified\n",
          MAX_INCPATHES);
  fprintf(stderr, "    (default is current directory)\n");
  closeFiles();
  exit(1);
} /* end showUsage */

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

static void parseArguments(int argc, char **argv)
{
  int i;

  programName = argv[0];

  /* Check for existence of at least the filename argument */

  if (argc < 2)
    {
      fprintf(stderr, "Invalid number of arguments\n");
      showUsage(); 
    }

  /* Parse any optional command line arguments */

  for (i = 1; i < argc-1; i++)
    {
      char *ptr = argv[i];
      if (ptr[0] == '-')
        {
          switch (ptr[1])
            {
            case 'I' :
              if (nIncPathes >= MAX_INCPATHES)
                {
                  fprintf(stderr, "Unrecognized [option]\n");
                  showUsage(); 
                }
              else
                {
                  includePath[nIncPathes] = &ptr[2];
                  nIncPathes++;
                }
              break;
            default:
              fprintf(stderr, "Unrecognized [option]\n");
              showUsage(); 
            }
        }
      else
        {
          fprintf(stderr, "Unrecognized [option]\n");
          showUsage(); 
        }
    }

  /* Extract the Pascal program name from the command line */

  sourceFileName = argv[argc-1];
}

/***********************************************************************
 * Public Functions
 ***********************************************************************/

int main(int argc, char *argv[])
{
  char filename [FNAME_SIZE+1];

  /* Parse command line arguments */

  parseArguments(argc, argv);

  /* Open all output files */

  openOutputFiles();

#if !LSTTOFILE
  lstFile = stdout;
#endif

  /* Open source file -- Use .PAS or command line extension, if supplied */

  (void)extension(sourceFileName, "PAS", filename, 0);
  fprintf(errFile, "%01x=%s\n", FP->include, filename);

  memset(FP, 0, sizeof(fileState_t));
  FP->stream = fopen(filename, "r");
  if (!FP->stream)
    {
      errmsg("Could not open source file '%s': %s\n",
             filename, strerror(errno));
      showUsage(); 
    }
   
  /* Initialization */

  primeSignalHandlers();
  primeSymbolTable(MAX_SYM);
  primeBuiltInProcedures();
  primeBuiltInFunctions();
  primeTokenizer(MAX_STRINGS);

  /* Initialize the POFF object */

  poffHandle = poffCreateHandle();
  if (poffHandle == NULL)
    fatal(eNOMEMORY);

  /* Save the soure file name in the POFF output file */

  FP->include = poffAddFileName(poffHandle, filename);

  /* Define standard input/output file characteristics */

  files[0].defined = -1;
  files[0].flevel  = level;
  files[0].ftype   = sCHAR;
  files[0].faddr   = dstack;
  files[0].fsize   = sCHAR_SIZE;
  dstack          += sCHAR_SIZE;

  /* We need the following in order to calculate relative stack positions. */

  FP->dstack       = dstack;

  /* Indicate that no WITH statement has been processed */

  memset(&withRecord, 0, sizeof(WTYPE));

  /* Process the pascal program
   *
   * FORM: pascal = program | unit
   * FORM: program = program-heading ';' [uses-section ] block '.'
   * FORM: program-heading = 'program' identifier [ '(' identifier-list ')' ]
   * FORM: unit = unit-heading ';' interface-section implementation-section init-section
   * FORM: unit-heading = 'unit' identifer
   */

  getToken();
  if (token == tPROGRAM)
    { 
      /* Compile a pascal program */

      FP->kind    = eIsProgram;
      FP->section = eIsProgramSection;
      getToken();
      program();
    }
  else if (token == tUNIT)
    {
      /* Compile a pascal unit */

      FP->kind    = eIsUnit;
      FP->section = eIsOtherSection;
      getToken();
      unitImplementation();
    }
  else
    {
      /* Expected 'program' or 'unit' */

      error(ePROGRAM);
    }

  /* Dump the symbol table content (debug only) */

#if CONFIG_DEBUG
  dumpTables();
#endif

  /* Write the POFF output file */

  poffWriteFile(poffHandle, poffFile);
  poffDestroyHandle(poffHandle);

  /* Close all output files */

  closeFiles();

  /* Write Closing Message */

  if (warn_count > 0)
    {
      printf("  %ld Warnings Issued\n", warn_count);
    } /* end if */

  if (err_count > 0)
    {
      printf("  %d Errors Detected\n\n", err_count);
      return -1;
    } /* end if */

  return 0;

} /* end main */

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

void openNestedFile(const char *fileName)
{
  fileState_t *prev = FP;
  char fullpath[FNAME_SIZE + 1];
  int i;

  /* Make sure we can handle another nested file */

  if (++includeIndex >= MAX_INCL) fatal(eOVF);
  else
    {
      /* Clear the file state structure for the new include level */

      memset(FP, 0, sizeof(fileState_t));

      /* Try all source include pathes until we find the file or
       * until we exhaust the include path list.
       */

      for (i = 0; ; i++)
        {
          /* Open the nested file -- try all possible pathes or
           * until we successfully open the file.
           */

          /* The final path that we will try is the current directory */

          if (i == nIncPathes)
            {
              sprintf(fullpath, "./%s", fileName);
            }
          else
            {
              sprintf(fullpath, "%s/%s", includePath[i], fileName);
            }

          FP->stream = fopen (fullpath, "rb");
          if (!FP->stream)
            {
              /* We failed to open the file.  If there are no more
               * include pathes to examine (including the current directory),
               * then error out.  This is fatal.  Otherwise, continue
               * looping.
               */

              if (i == nIncPathes)
                {
                  errmsg("Failed to open '%s': %s\n",
                         fileName, strerror(errno));
                  fatal(eINCLUDE);
                  break; /* Won't get here */
                }
            } /* end else if */
          else
            break;
        }

      /* Setup the newly opened file */

      fprintf(errFile, "%01x=%s\n", FP->include, fullpath);
      FP->include = poffAddFileName(poffHandle, fullpath);

      /* The caller may change this, but the default behavior is
       * to inherit the kind and section of the including file
       * and the current data stack offset.
       */

      FP->kind    = prev->kind;
      FP->section = prev->section;
      FP->dstack  = dstack;

      rePrimeTokenizer();

      /* Get the first token from the file */

      getToken();
    } /* end else */
}

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

void closeNestedFile(void)
{
  if (FP->stream)
    {
      (void)fclose(FP->stream);
      includeIndex--;
    }
}

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