/**********************************************************************
 * punit.c
 * Parse a pascal unit file
 *
 *   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
 **********************************************************************/

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>

#include "keywords.h"
#include "pasdefs.h"
#include "ptdefs.h"
#include "podefs.h"
#include "pedefs.h"
#include "poff.h"      /* FHT_ definitions */

#include "pas.h"       /* for globals */
#include "pblck.h"     /* for block(), constantDefinitionGroup(), etc. */
#include "pgen.h"      /* for pas_Generate*() */
#include "ptkn.h"      /* for getToken() */
#include "ptbl.h"      /* for addFile() */
#include "pofflib.h"   /* For poff*() functions*/
#include "perr.h"      /* for error() */
#include "pprgm.h"     /* for usesSection() */
#include "punit.h"

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

#define intAlign(x)     (((x) + (sINT_SIZE-1)) & (~(sINT_SIZE-1)))

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

static void interfaceSection          (void);
static void exportedProcedureHeading  (void);
static void exportedFunctionHeading   (void);

/***********************************************************************
 * Global Functions
 ***********************************************************************/
/* This function is called only main() when the first token parsed out
 * the specified file is 'unit'.  In this case, we are parsing a unit file
 * and generating a unit binary.
 */

void unitImplementation(void)
{
  char   *saveTknStart = tkn_strt;

  TRACE(lstFile, "[unitImplementation]");

  /* FORM: unit =
   *       unit-heading ';' interface-section implementation-section
   *       init-section '.'
   * FORM: unit-heading = 'unit' identifer
   * FORM: interface-section =
   *       'interface' [ uses-section ] interface-declaration
   * FORM: implementation-section =
   *       'implementation' [ uses-section ] declaration-group
   * FORM: init-section =
   *       'initialization statement-sequence
   *       ['finalization' statement-sequence] 'end' |
   *       compound-statement | 'end'
   *
   * On entry, the 'unit' keyword has already been parsed.  The
   * current token should point to the identifier following unit.
   */

  /* Skip over the unit identifier (the caller has already verified
   * that we are processing the correct unit).
   */

  if (token != tIDENT) error(eIDENT);

  /* Set a UNIT indication in the output poff file header */

  poffSetFileType(poffHandle, FHT_UNIT, 0, tkn_strt);
  poffSetArchitecture(poffHandle, FHA_PCODE);

  /* Discard the unit name and get the next token */

  stringSP = saveTknStart;
  getToken();

  /* Skip over the semicolon separating the unit-heading from the
   * interface-section.
   */

  if (token != ';') error(eSEMICOLON);
  else getToken();

  /* Verify that the interface-section is present
   * FORM: interface-section =
   *       'interface' [ uses-section ] interface-declaration
   */

  interfaceSection();

  /* Verify that the implementation section is present
   * FORM: implementation-section =
   *       'implementation' [ uses-section ] declaration-group
   */

  if (token != tIMPLEMENTATION) error(eIMPLEMENTATION);
  else getToken();

  FP->section = eIsImplementationSection;

  /* Check for the presence of an optional uses-section */

  if (token == tUSES)
    {
      /* Process the uses-section */

      getToken();
      usesSection();
    }

  /* Now, process the declaration-group
   *
   * FORM: implementation-section =
   *       'implementation' [ uses-section ] declaration-group
   * FORM: init-section =
   *       'initialization statement-sequence
   *       ['finalization' statement-sequence] 'end' |
   *       compound-statement | 'end'
   */

  declarationGroup(0);

  /* Process the init-section
   * FORM: init-section =
   *       'initialization statement-sequence
   *       ['finalization' statement-sequence] 'end' |
   *       compound-statement | 'end'
   *
   * Not yet... for now, we only require the 'end'
   */

  FP->section = eIsInitializationSection;
  if (token != tEND) error(eEND);
  else getToken();

  FP->section = eIsOtherSection;

  /* Verify that the unit file ends with a period */

  if (token != '.') error(ePERIOD);
}

/***********************************************************************/
/* This logic is called from usersSection after any a uses-section is
 * encountered in any file at any level.  In this case, we are only
 * going to parse the interface section from the unit file.
 */

void unitInterface(void)
{
  int32_t savedDStack  = dstack;
  TRACE(lstFile, "[unitInterface]");

  /* FORM: unit =
   *       unit-heading ';' interface-section implementation-section
   *       init-section
   * FORM: unit-heading = 'unit' identifer
   *
   * On entry, the 'unit' keyword has already been parsed.  The
   * current token should point to the identifier following unit.
   */

  /* Skip over the unit identifier (the caller has already verified
   * that we are processing the correct unit).
   */

  if (token != tIDENT) error(eIDENT);
  else getToken();

  /* Skip over the semicolon separating the unit-heading from the
   * interface-section.
   */

  if (token != ';') error(eSEMICOLON);
  else getToken();

  /* Process the interface-section
   * FORM: interface-section =
   *       'interface' [ uses-section ] interface-declaration
   */

  interfaceSection();

  /* Verify that the implementation section is present
   * FORM: implementation-section =
   *       'implementation' [ uses-section ] declaration-group
   */

  if (token != tIMPLEMENTATION) error(eIMPLEMENTATION);

  /* Then just ignore the rest of the file.  We'll let the compilation
   * of the unit file check the correctness of the implementation.
   */

  FP->section = eIsOtherSection;

  /* If we are generating a program binary, then all variables declared
   * by this logic a bonafide.  But if are generating UNIT binary, then
   * all variables declared as imported with a relative stack offset.
   * In this case, we must release any data stack allocated in this
   * process.
   */

  dstack = savedDStack;
}

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

static void interfaceSection(void)
{
  int16_t saveNSym   = nsym;          /* Save top of symbol table */
  int16_t saveNConst = nconst;        /* Save top of constant table */

  TRACE(lstFile, "[interfaceSection]");

  /*  FORM: interface-section =
   *       'interface' [ uses-section ] interface-declaration
   *
   * On entry, the unit-heading keyword has already been parsed.  The
   * current token should point to the identifier following unit.
   */

  if (token != tINTERFACE) error(eINTERFACE);
  else getToken();

  FP->section = eIsInterfaceSection;

  /* Check for the presence of an optional uses-section */

  if (token == tUSES)
    {
      /* Process the uses-section */

      getToken();
      usesSection();
    }

  /* Process the interface-declaration
   *
   * FORM: interface-declaration =
   *       [ constant-definition-group ] [ type-definition-group ]
   *       [ variable-declaration-group ] exported-heading
   */

   /* Process optional constant-definition-group.
    * FORM: constant-definition-group =
    *       'const' constant-definition ';' { constant-definition ';' }
    */

   if (token == tCONST)
     {
       const_strt = saveNConst;        /* Limit search to present level */
       getToken();                     /* Get identifier */
       const_strt = 0;

       /* Process constant-definition.
        * FORM: constant-definition = identifier '=' constant
        */

       constantDefinitionGroup();

     } /* end if */

   /* Process type-definition-group
    * FORM: type-definition-group =
    *       'type' type-definition ';' { type-definition ';' }
    */

   if (token == tTYPE)
     {
       const_strt = saveNConst;        /* Limit search to present level */
       sym_strt   = saveNSym;
       getToken();                     /* Get identifier */
       const_strt = 0;
       sym_strt   = 0;

       /* Process the type-definitions in the type-definition-group
        * FORM: type-definition = identifier '=' type-denoter
        */

       typeDefinitionGroup();
     } /* end if */

   /* Process the optional variable-declaration-group
    * FORM: variable-declaration-group =
    *       'var' variable-declaration { ';' variable-declaration }
    */

   if (token == tVAR)
     {
       const_strt = saveNConst;        /* Limit search to present level */
       sym_strt   = saveNSym;
       getToken();                     /* Get identifier */
       const_strt = 0;
       sym_strt   = 0;

       /* Process the variable declarations
        * FORM: variable-declaration = identifier-list ':' type-denoter
        * FORM: identifier-list = identifier { ',' identifier }
        */

       variableDeclarationGroup();
     } /* end if */

   /* Process the exported-heading
    *
    * FORM: exported-heading = 
    *       procedure-heading ';' [ directive ] |
    *       function-heading ';' [ directive ]
    */

   for (;;)
     {
       /* FORM: function-heading =
        *       'function' function-identifier [ formal-parameter-list ]
        *        ':' result-type
        */

       if (token == tFUNCTION)
         {
           const_strt = saveNConst;    /* Limit search to present level */
           sym_strt   = saveNSym;
           getToken();                 /* Get identifier */
           const_strt = 0;
           sym_strt   = 0;

           /* Process the interface declaration */

           exportedFunctionHeading();
         } /* end if */

       /* FORM: procedure-heading =
        *       'procedure' procedure-identifier [ formal-parameter-list ]
        */

       else if (token == tPROCEDURE)
         {
           const_strt = saveNConst;    /* Limit search to present level */
           sym_strt   = saveNSym;
           getToken();                 /* Get identifier */
           const_strt = 0;
           sym_strt   = 0;

           /* Process the interface declaration */

           exportedProcedureHeading();
         } /* end else if */
       else break;
     } /* end for */

   /* We are finished with the interface section */

   FP->section = eIsOtherSection;
}

/* Process Procedure Declaration Block */

static void exportedProcedureHeading(void)
{
   uint16_t procLabel = ++label;
   char    *saveChSp;
   STYPE   *procPtr;
   register int i;

   TRACE(lstFile,"[exportedProcedureHeading]");

   /* FORM: procedure-heading =
    *       'procedure' identifier [ formal-parameter-list ]
    * FORM: procedure-identifier = identifier
    *
    * On entry, token refers to token AFTER the 'procedure' reserved
    * word.
    */

   /* Process the procedure-heading */

   if (token != tIDENT)
     {
       error (eIDENT);
       return;
     } /* endif */

   procPtr = addProcedure(tkn_strt, sPROC, procLabel, 0, NULL);

   /* Mark the procedure as external */

   procPtr->sParm.p.flags |= SPROC_EXTERNAL;

   /* Save the string stack pointer so that we can release all
    * formal parameter strings later.  Then get the next token.
    */

   saveChSp = stringSP;
   getToken();

   /* NOTE:  The level associated with the PROCEDURE symbol is the level
    * At which the procedure was declared.  Everything declare within the
    * PROCEDURE is at the next level
    */

   level++;

   /* Process parameter list */

   (void)formalParameterList(procPtr);

   if (token !=  ';') error (eSEMICOLON);
   else getToken();

   /* If we are compiling a program or unit that "imports" the 
    * procedure then generate the appropriate symbol table entries 
    * in the output file to support relocation when the external
    * procedure is called.
    */

   if (includeIndex > 0)
     {
       pas_GenerateProcImport(procPtr);
     }

   /* Destroy formal parameter names */

   for (i = 1; i <= procPtr->sParm.p.nParms; i++)
     {
       procPtr[i].sName = NULL;
     }
   stringSP = saveChSp;

   /* Drop the level back to where it was */

   level--;

} /* end exportedProcedureHeading */

/***************************************************************/
/* Process Function Declaration Block */

static void exportedFunctionHeading(void)
{
   uint16_t funcLabel = ++label;
   int16_t  parameterOffset;
   char    *saveChSp;
   STYPE   *funcPtr;
   register int i;

   TRACE(lstFile,"[exportedFunctionHeading]");

   /* FORM: function-declaration =
    *       function-heading ';' directive |
    *       function-heading ';' function-block
    * FORM: function-heading =
    *       'function' function-identifier [ formal-parameter-list ]
    *       ':' result-type
    *
    * On entry token should lrefer to the function-identifier.
    */

   /* Verify function-identifier */

   if (token != tIDENT)
     {
       error (eIDENT);
       return;
     } /* endif */

   funcPtr = addProcedure(tkn_strt, sFUNC, funcLabel, 0, NULL);

   /* Mark the procedure as external */

   funcPtr->sParm.p.flags |= SPROC_EXTERNAL;

   /* NOTE:  The level associated with the FUNCTION symbol is the level
    * At which the procedure was declared.  Everything declare within the
    * PROCEDURE is at the next level
    */

   level++;

   /* Save the string stack pointer so that we can release all
    * formal parameter strings later.  Then get the next token.
    */

   saveChSp = stringSP;
   getToken();

   /* Process parameter list */

   parameterOffset = formalParameterList(funcPtr);

   /* Verify that the parameter list is followed by a colon */

   if (token !=  ':') error (eCOLON);
   else getToken();

   /* Get function type, return value type/size and offset to return value */

   if (token == sTYPE)
     {
       /* The offset to the return value is the offset to the last
        * parameter minus the size of the return value (aligned to
        * multiples of size of INTEGER).
        */

       parameterOffset        -= tknPtr->sParm.t.rsize;
       parameterOffset         = intAlign(parameterOffset);

       /* Save the TYPE for the function */

       funcPtr->sParm.p.parent = tknPtr;

       /* Skip over the result-type token */

       getToken();
     } /* end if */
   else
     {
       error(eINVTYPE);
     }

   /* Verify the final semicolon */

   if (token !=  ';') error (eSEMICOLON);
   else getToken();

   /* If we are compiling a program or unit that "imports" the 
    * function then generate the appropriate symbol table entries 
    * in the output file to support relocation when the external
    * function is called.
    */

   if (includeIndex > 0)
     {
       pas_GenerateProcImport(funcPtr);
     }

   /* Destroy formal parameter names and the function return value name */

   for (i = 1; i <= funcPtr->sParm.p.nParms; i++)
     {
       funcPtr[i].sName = ((char *) NULL);
     }
   stringSP = saveChSp;

   /* Restore the original level */

   level--;

} /* end exportedFunctionHeading */
