/****************************************************************************
 * pstm.c
 * Pascal Statements
 *
 *   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 <stdbool.h>
#include <stdio.h>

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

#include "pas.h"
#include "pstm.h"
#include "pproc.h"
#include "pexpr.h"
#include "pgen.h"
#include "ptkn.h"
#include "ptbl.h"
#include "pinsn.h"
#include "perr.h"

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

#define ADDRESS_DEREFERENCE  0x01
#define ADDRESS_ASSIGNMENT   0x02
#define INDEXED_ASSIGNMENT   0x04
#define VAR_PARM_ASSIGNMENT  0x08

#define isConstant(x) \
        (  ((x) == tINT_CONST) \
        || ((x) == tBOOLEAN_CONST) \
        || ((x) == tCHAR_CONST) \
        || ((x) == tREAL_CONST) \
        || ((x) == sSCALAR_OBJECT))

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

/* Assignment Statements */

static void pas_ComplexAssignment(void);
static void pas_SimpleAssignment (STYPE *varPtr, uint8_t assignFlags);
static void pas_Assignment       (uint16_t storeOp, exprType assignType, STYPE *varPtr, STYPE *typePtr);
static void pas_StringAssignment (STYPE *varPtr, STYPE *typePtr);
static void pas_LargeAssignment  (uint16_t storeOp, exprType assignType, STYPE *varPtr, STYPE *typePtr);

/* Other Statements */

static void pas_GotoStatement    (void);  /* GOTO statement */
static void pas_LabelStatement   (void);  /* Label statement */
static void pas_ProcStatement    (void);  /* Procedure method statement */
static void pas_IfStatement      (void);  /* IF-THEN[-ELSE] statement */
static void pas_CaseStatement    (void);  /* Case statement */
static void pas_RepeatStatement  (void);  /* Repeat statement */
static void pas_WhileStatement   (void);  /* While statement */
static void pas_ForStatement     (void);  /* For statement */
static void pas_WithStatement    (void);  /* With statement */

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

void statement(void)
{
  STYPE *symPtr;     /* Save Symbol Table pointer to token */

  TRACE(lstFile,"[statement");

  /* Generate file/line number pseudo-operation to facilitate P-Code testing */

  pas_GenerateLineNumber(FP->include, FP->line);

  /* We will push the string stack pointer at the beginning of each
   * statement and pop the string stack pointer at the end of each
   * statement.  Subsequent optimization logic will scan the generated
   * pcode to ascertain if the push and pops were necessary.  They
   * would be necessary if expression parsing generated temporary usage
   * of string stack storage.  In this case, the push will save the
   * value before the temporary usage and the pop will release the
   * temporaray storage.
   */

  pas_GenerateSimple(opPUSHS);

  /* Process the statement according to the type of the leading token */

  switch (token)
    {
      /* Simple assignment statements */

    case sINT :
      symPtr = tknPtr;
      getToken();
      pas_Assignment(opSTS, exprInteger, symPtr, symPtr->sParm.v.parent);
      break;
    case sCHAR :
      symPtr = tknPtr;
      getToken();
      pas_Assignment(opSTSB, exprChar, symPtr, symPtr->sParm.v.parent);
      break;
    case sBOOLEAN :
      symPtr = tknPtr;
      getToken();
      pas_Assignment(opSTSB, exprBoolean, symPtr, NULL);
      break;
    case sREAL :
      symPtr = tknPtr;
      getToken();
      pas_LargeAssignment(opSTSM, exprReal, symPtr, symPtr->sParm.v.parent);
      break;
    case sSCALAR :
      symPtr = tknPtr;
      getToken();
      pas_Assignment(opSTS, exprScalar, symPtr, symPtr->sParm.v.parent);
      break;
    case sSET_OF :
      symPtr = tknPtr;
      getToken();
      pas_Assignment(opSTS, exprSet, symPtr, symPtr->sParm.v.parent);
      break;
    case sSTRING :
    case sRSTRING :
      symPtr = tknPtr;
      getToken();
      pas_StringAssignment(symPtr, symPtr->sParm.v.parent);
      break;

      /* Complex assignments statements */

    case sSUBRANGE :
    case sRECORD :
    case sRECORD_OBJECT :
    case sPOINTER :
    case sVAR_PARM :
    case sARRAY :
      pas_ComplexAssignment();
      break;

      /* Branch, Call and Label statements */

    case sPROC         : pas_ProcStatement(); break;
    case tGOTO         : pas_GotoStatement(); break;
    case tINT_CONST    : pas_LabelStatement(); break;

      /* Conditional Statements */

    case tIF           : pas_IfStatement(); break;
    case tCASE         : pas_CaseStatement(); break;

      /* Loop Statements */

    case tREPEAT       : pas_RepeatStatement(); break;
    case tWHILE        : pas_WhileStatement(); break;
    case tFOR          : pas_ForStatement(); break;

      /* Other Statements */

    case tBEGIN        : compoundStatement(); break;
    case tWITH         : pas_WithStatement(); break;

      /* None of the above, try standard procedures */
    default            : builtInProcedure(); break;

  } /* end switch */

  /* Generate the POPS that matches the PUSHS generated at the begining
   * of this function (see comments above).
   */

  pas_GenerateSimple(opPOPS);

  TRACE(lstFile,"]");

} /* end statement */

/***********************************************************************/
/* Process a complex assignment statement */

static void pas_ComplexAssignment(void)
{
   STYPE symbolSave;
   TRACE(lstFile,"[pas_ComplexAssignment]");

   /* FORM:  <variable OR function identifer> := <expression>
    * First, make a copy of the symbol table entry because the call to
    * pas_SimpleAssignment() will modify it.
    */

   symbolSave = *tknPtr;
   getToken();

   /* Then process the complex assignment until it is reduced to a simple
    * assignment (like int, char, etc.)
    */

   pas_SimpleAssignment(&symbolSave, 0);
}

/***********************************************************************/
/* Process a complex assignment (recursively) until it becomes a
 * simple assignment statement
 */

static void pas_SimpleAssignment(STYPE *varPtr, uint8_t assignFlags)
{
  STYPE *typePtr;
  TRACE(lstFile,"[pas_SimpleAssignment]");

  /* FORM:  <variable OR function identifer> := <expression> */

  typePtr = varPtr->sParm.v.parent;
  switch (varPtr->sKind)
    {
      /* Check if we have reduce the complex assignment to a simple
       * assignment yet
       */

    case sINT :
      if ((assignFlags & INDEXED_ASSIGNMENT) != 0)
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDSX, varPtr);
              pas_Assignment(opSTI, exprInteger, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTSX, exprIntegerPtr, varPtr, typePtr);
          else 
            pas_Assignment(opSTSX, exprInteger, varPtr, typePtr);
        } /* end if */
      else
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDS, varPtr);
              pas_Assignment(opSTI, exprInteger, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTS, exprIntegerPtr, varPtr, typePtr);
          else
            pas_Assignment(opSTS, exprInteger, varPtr, typePtr);
        } /* end else */
      break;
    case sCHAR :
      if ((assignFlags & INDEXED_ASSIGNMENT) != 0)
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDSX, varPtr);
              pas_Assignment(opSTIB, exprChar, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTSX, exprCharPtr, varPtr, typePtr);
          else 
            pas_Assignment(opSTSXB, exprChar, varPtr, typePtr);
        } /* end if */
      else
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDS, varPtr);
              pas_Assignment(opSTIB, exprChar, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTS, exprCharPtr, varPtr, typePtr);
          else
            pas_Assignment(opSTSB, exprChar, varPtr, typePtr);
        } /* end else */
      break;
    case sBOOLEAN :
      if ((assignFlags & INDEXED_ASSIGNMENT) != 0)
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDSX, varPtr);
              pas_Assignment(opSTI, exprBoolean, varPtr, NULL);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTSX, exprBooleanPtr, varPtr, typePtr);
          else 
            pas_Assignment(opSTSX, exprBoolean, varPtr, NULL);
        } /* end if */
      else
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDS, varPtr);
              pas_Assignment(opSTI, exprBoolean, varPtr, NULL);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTS, exprBooleanPtr, varPtr, typePtr);
          else
            pas_Assignment(opSTS, exprBoolean, varPtr, NULL);
        } /* end else */
      break;
    case sREAL         :
      if ((assignFlags & INDEXED_ASSIGNMENT) != 0)
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDSX, varPtr);
              pas_LargeAssignment(opSTIM, exprReal, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTSX, exprRealPtr, varPtr, typePtr);
          else 
            pas_LargeAssignment(opSTSXM, exprReal, varPtr, typePtr);
        } /* end if */
      else
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDS, varPtr);
              pas_LargeAssignment(opSTIM, exprReal, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTS, exprRealPtr, varPtr, typePtr);
          else
            pas_LargeAssignment(opSTSM, exprReal, varPtr, typePtr);
        } /* end else */
      break;
    case sSCALAR :
      if ((assignFlags & INDEXED_ASSIGNMENT) != 0)
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDSX, varPtr);
              pas_Assignment(opSTI, exprScalar, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTSX, exprScalarPtr, varPtr, typePtr);
          else 
            pas_Assignment(opSTSX, exprScalar, varPtr, typePtr);
        } /* end if */
      else
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDS, varPtr);
              pas_Assignment(opSTI, exprScalar, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTS, exprScalarPtr, varPtr, typePtr);
          else
            pas_Assignment(opSTS, exprScalar, varPtr, typePtr);
        } /* end else */
      break;
    case sSET_OF :
      if ((assignFlags & INDEXED_ASSIGNMENT) != 0)
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDSX, varPtr);
              pas_Assignment(opSTI, exprSet, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTSX, exprSetPtr, varPtr, typePtr);
          else 
            pas_Assignment(opSTSX, exprSet, varPtr, typePtr);
        } /* end if */
      else
        {
          if ((assignFlags & ADDRESS_DEREFERENCE) != 0)
            {
              pas_GenerateStackReference(opLDS, varPtr);
              pas_Assignment(opSTI, exprSet, varPtr, typePtr);
            } /* end if */
          else if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
            pas_Assignment(opSTS, exprSetPtr, varPtr, typePtr);
          else
            pas_Assignment(opSTS, exprSet, varPtr, typePtr);
        } /* end else */
      break;

      /* NOPE... recurse until it becomes a simple assignment */

    case sSUBRANGE :
      varPtr->sKind = typePtr->sParm.t.subType;
      pas_SimpleAssignment(varPtr, assignFlags);
      break;

    case sRECORD :
      /* FORM: <record identifier>.<field> := <expression>
       * OR:   <record pointer identifier> := <pointer expression>
       */

      /* Check if this is a pointer to a record */

      if ((assignFlags & ADDRESS_ASSIGNMENT) != 0)
        {
          if (token == '.') error(ePOINTERTYPE);

          if ((assignFlags & INDEXED_ASSIGNMENT) != 0)
            pas_Assignment(opSTSX, exprRecordPtr, varPtr, typePtr);
          else
            pas_Assignment(opSTS, exprRecordPtr, varPtr, typePtr);
        } /* end if */
      else if (((assignFlags & ADDRESS_DEREFERENCE) != 0) &&
               ((assignFlags & VAR_PARM_ASSIGNMENT) == 0))
        error(ePOINTERTYPE);

      /* Check if a period separates the RECORD identifier from the
       * record field identifier
       */

      else if (token == '.')
        {
          /* Skip over the period */

          getToken();

          /* Verify that a field identifier associated with this record
           * follows the period.
           */

          if ((token != sRECORD_OBJECT) ||
              (tknPtr->sParm.r.record != typePtr))
            error(eRECORDOBJECT);
          else
            {
              /* Modify the variable so that it has the characteristics of the
               * the field but with level and offset associated with the record
               */

              typePtr                 = tknPtr->sParm.r.parent;
              varPtr->sKind           = typePtr->sParm.t.type;
              varPtr->sParm.v.parent  = typePtr;

              /* Special case:  The record is a VAR parameter. */

              if (assignFlags == (INDEXED_ASSIGNMENT | ADDRESS_DEREFERENCE | VAR_PARM_ASSIGNMENT))
                {
                  pas_GenerateDataOperation(opPUSH, tknPtr->sParm.r.offset);
                  pas_GenerateSimple(opADD);
                } /* end if */
              else
                varPtr->sParm.v.offset += tknPtr->sParm.r.offset;

              getToken();
              pas_SimpleAssignment(varPtr, assignFlags);

            } /* end else if */
        } /* end else */

      /* It must be a RECORD assignment */

      else
        {
          /* Special case:  The record is a VAR parameter. */

          if (assignFlags == (INDEXED_ASSIGNMENT | ADDRESS_DEREFERENCE | VAR_PARM_ASSIGNMENT))
            {
              pas_GenerateStackReference(opLDS, varPtr);
              pas_GenerateSimple(opADD);
              pas_LargeAssignment(opSTIM, exprRecord, varPtr, typePtr);
            } /* end if */
          else
            pas_LargeAssignment(opSTSM, exprRecord, varPtr, typePtr);
        } /* end else */
      break;

    case sRECORD_OBJECT :
      /* FORM: <field> := <expression>
       * NOTE:  This must have been preceeded with a WITH statement
       * defining the RECORD type
       */

      if (!withRecord.parent)
        error(eINVTYPE);
      else if ((assignFlags && (ADDRESS_DEREFERENCE | ADDRESS_ASSIGNMENT)) != 0)
        error(ePOINTERTYPE);
      else if ((assignFlags && INDEXED_ASSIGNMENT) != 0)
        error(eARRAYTYPE);

      /* Verify that a field identifier is associated with the RECORD
       * specified by the WITH statement.
       */

      else if (varPtr->sParm.r.record != withRecord.parent)
        error(eRECORDOBJECT);

      else
        {
          int16_t tempOffset;

          /* Now there are two cases to consider:  (1) the withRecord is a
           * pointer to a RECORD, or (2) the withRecord is the RECORD itself
           */

          if (withRecord.pointer)
            {
              /* If the pointer is really a VAR parameter, then other syntax
               * rules will apply
               */

              if (withRecord.varParm)
                assignFlags |= (INDEXED_ASSIGNMENT | ADDRESS_DEREFERENCE | VAR_PARM_ASSIGNMENT);
              else
                assignFlags |= (INDEXED_ASSIGNMENT | ADDRESS_DEREFERENCE);

              pas_GenerateDataOperation(opPUSH, (varPtr->sParm.r.offset + withRecord.index));
              tempOffset     = withRecord.offset;
            } /* end if */
          else
            {
              tempOffset   = varPtr->sParm.r.offset + withRecord.offset;
            } /* end else */

          /* Modify the variable so that it has the characteristics of the
           * the field but with level and offset associated with the record
           * NOTE:  We have to be careful here because the structure
           * associated with sRECORD_OBJECT is not the same as for
           * variables!
           */

          typePtr                 = varPtr->sParm.r.parent;

          varPtr->sKind           = typePtr->sParm.t.type;
          varPtr->sLevel          = withRecord.level;
          varPtr->sParm.v.size    = typePtr->sParm.t.asize;
          varPtr->sParm.v.offset  = tempOffset;
          varPtr->sParm.v.parent  = typePtr;

          pas_SimpleAssignment(varPtr, assignFlags);

        } /* end else */
      break;

    case sPOINTER :
      /* FORM: <pointer identifier>^ := <expression>
       * OR:   <pointer identifier> := <pointer expression>
       */

      if (token == '^') /* value assignment? */
        {
          getToken();
          assignFlags |= ADDRESS_DEREFERENCE;
        } /* end if */
      else
        assignFlags |= ADDRESS_ASSIGNMENT;

      varPtr->sKind = typePtr->sParm.t.type;
      pas_SimpleAssignment(varPtr, assignFlags);
      break;

    case sVAR_PARM :
      if (assignFlags != 0) error(eVARPARMTYPE);
      assignFlags |= (ADDRESS_DEREFERENCE | VAR_PARM_ASSIGNMENT);

      varPtr->sKind = typePtr->sParm.t.type;
      pas_SimpleAssignment(varPtr, assignFlags);
      break;

    case sARRAY :
      /* FORM: <array identifier> := <expression>
       * OR:   <pointer array identifier>[<index>]^ := <expression>
       * OR:   <pointer array identifier>[<index>] := <pointer expression>
       * OR:   <record array identifier>[<index>].<field identifier> := <expression>
       * OR:   etc., etc., etc.
       */

      if (assignFlags != 0) error(eARRAYTYPE);
      assignFlags |= INDEXED_ASSIGNMENT;

      arrayIndex(typePtr->sParm.t.asize);
      varPtr->sKind        = typePtr->sParm.t.type;
      varPtr->sParm.v.size = typePtr->sParm.t.asize;
      pas_SimpleAssignment(varPtr, assignFlags);
      break;

    default :
      error(eINVTYPE);
      break;

    }
}

/***********************************************************************/
/* Process simple assignment statement */

static void pas_Assignment(uint16_t storeOp, exprType assignType,
                           STYPE *varPtr, STYPE *typePtr)
{
   TRACE(lstFile,"[pas_Assignment]");

   /* FORM:  <variable OR function identifer> := <expression> */

   if (token != tASSIGN) error (eASSIGN);
   else getToken();

   expression(assignType, typePtr);
   pas_GenerateStackReference(storeOp, varPtr);
}

/***********************************************************************/
/* Process the assignment to a variable length string record */

static void pas_StringAssignment(STYPE *varPtr, STYPE *typePtr)
{
  exprType stringKind;

   TRACE(lstFile,"[pas_StringAssignment]");

   /* FORM:  <variable OR function identifer> := <expression> */

   /* Verify that the assignment token follows the indentifier */

   if (token != tASSIGN) error (eASSIGN);
   else getToken();

   /* Get the expression after assignment token. We'll take any kind
    * of string expression.  This is a hack to handle calls to system
    * functions that return exprCString pointers that must be converted
    * to exprString records upon assignment.
    */

   stringKind = expression(exprAnyString, typePtr);

   /* Place the address of the destination string structure instance on the
    * stack.
    */

   pas_GenerateStackReference(opLAS, varPtr);

   /* Check if this is an assignment to a global allocated string, or
    * to a stack reference to an allocated string.
    */

   if (varPtr->sKind == sRSTRING)
     {
       /* It is an assignment to a string reference --
        * Generate a runtime library call to copy the destination
        * string string into the pascal string instance.  The particular
        * runtime call will account for any necesary string type conversion.
        */

       if ((stringKind == exprString) || (stringKind == exprStkString))
         {
           /* It is a pascal string type. Current stack representation is:
            *
            *   TOS(0)=address of dest string reference
            *   TOS(1)=length of source string
            *   TOS(2)=pointer to source string
            */

           pas_BuiltInFunctionCall(lbSTR2RSTR);
         }
       else if (stringKind == exprCString)
         {
           /* It is a 32-bit C string point.  Current stack representation is:
            *
            *   TOS(0)=address of dest string reference
            *   TOS(1)=MS 16-bits of 32-bit C source string pointer
            *   TOS(2)=LS 16-bits of 32-bit C source string pointer
            */

           pas_BuiltInFunctionCall(lbCSTR2RSTR);
         }
     }
   else
     {
       /* It is an assignment to a allocated Pascal string --
        * Generate a runtime library call to copy the destination
        * string string into the pascal string instance.  The particular
        * runtime call will account for any necesary string type conversion.
        */

       if ((stringKind == exprString) || (stringKind == exprStkString))
         {
           /* It is a pascal string type. Current stack representation is:
            *
            *   TOS(0)=address of dest string hdr
            *   TOS(1)=length of source string
            *   TOS(2)=pointer to source string
            */

           pas_BuiltInFunctionCall(lbSTR2STR);
         }
       else if (stringKind == exprCString)
         {
           /* It is a 32-bit C string point.  Current stack representation is:
            *
            *   TOS(0)=address of dest string hdr
            *   TOS(1)=MS 16-bits of 32-bit C source string pointer
            *   TOS(2)=LS 16-bits of 32-bit C source string pointer
            */

           pas_BuiltInFunctionCall(lbCSTR2STR);
         }
     }

   /* else ... type mismatch error already reported by expression() */
}

/***********************************************************************/
/* Process a multiple word assignment statement */

static void pas_LargeAssignment(uint16_t storeOp, exprType assignType,
                                STYPE *varPtr, STYPE *typePtr)
{
   TRACE(lstFile,"[pas_LargeAssignment]");

   /* FORM:  <variable OR function identifer> := <expression> */

   if (token != tASSIGN) error (eASSIGN);
   else getToken();

   expression(assignType, typePtr);
   pas_GenerateDataSize(varPtr->sParm.v.size);
   pas_GenerateStackReference(storeOp, varPtr);
}

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

static void pas_GotoStatement(void)
{
   char   labelname [8];                /* Label symbol table name */
   STYPE  *label_ptr;                   /* Pointer to Label Symbol */

   TRACE(lstFile,"[pas_GotoStatement]");

   /* FORM:  GOTO <integer> */

   /* Get the token after the goto reserved word. It should be an <integer> */

   getToken();
   if (token != tINT_CONST)
     {
       /* Token following the goto is not an integer */

       error(eINVLABEL);
     }
   else
     {
       /* The integer label must be non-negative */

       if (tknInt < 0)
         {
           error(eINVLABEL);
         }
       else
         {
           /* Find and verify the symbol associated with the label */

           (void)sprintf (labelname, "%ld", tknInt);
           if (!(label_ptr = findSymbol(labelname)))
             {
               error(eUNDECLABEL);
             }
           else if (label_ptr->sKind != sLABEL)
             {
               error(eINVLABEL);
             }
           else
             {
               /* Generate the branch to the label */

               pas_GenerateDataOperation(opJMP, label_ptr->sParm.l.label);
             }
         }

       /* Get the token after the <integer> value */

       getToken();
     }
}

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

static void pas_LabelStatement(void)
{
   char   labelName [8];                /* Label symbol table name */
   STYPE  *labelPtr;                    /* Pointer to Label Symbol */

   TRACE(lstFile,"[pas_LabelStatement]");

   /* FORM:  <integer> : */

   /* Verify that the integer is a label name */

   (void)sprintf (labelName, "%ld", tknInt);
   if (!(labelPtr = findSymbol(labelName)))
     {
       error(eUNDECLABEL);
     }
   else if(labelPtr->sKind != sLABEL)
     {
       error(eINVLABEL);
     }

   /* And also verify that the label symbol has not been previously
    * defined.
    */

   else if(!(labelPtr->sParm.l.unDefined))
     {
       error(eMULTLABEL);
     }
   else
     {
       /* Generate the label and indicate that it has been defined */

       pas_GenerateDataOperation(opLABEL, labelPtr->sParm.l.label);
       labelPtr->sParm.l.unDefined = false;

       /* We have to assume that we got here via a goto statement.
        * We don't have logic in place to track changes to the level
        * stack pointer (LSP) register, so we have no choice but to
        * invalidate that register now.
        */

       pas_InvalidateCurrentStackLevel();
     }

   /* Skip over the label integer */

   getToken();

   /* Make sure that the label is followed by a colon */

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

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

static void pas_ProcStatement(void)
{
  STYPE *procPtr = tknPtr;
  int size = 0;

  TRACE(lstFile,"[pas_ProcStatement]");

  /* FORM: procedure-method-statement =
   * procedure-method-specifier [ actual-parameter-list ]
   *
   * Skip over the procedure-method-statement
   */

  getToken();

  /* Get the actual parameters (if any) associated with the procedure
   * call.
   */

  size = actualParameterList(procPtr);

  /* Generate procedure call and stack adjustment (if required)
   * Upon return from the procedure, the level stack pointer (LSP)
   * may also be invalid.  However, we rely on level level logic in 
   * pgen.c to manage this case (as well as the function call case).
   */

  pas_GenerateProcedureCall(procPtr);
  if (size)
    {
      pas_GenerateDataOperation(opINDS, -size);
    }
}

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

static void pas_IfStatement(void)
{
  uint16_t else_label  = ++label;
  uint16_t endif_label = else_label;
  int32_t thenLSP;
  int32_t elseLSP;

  TRACE(lstFile,"[pas_IfStatement]");

  /* FORM: IF <expression> THEN <statement> [ELSE <statement>] */

  /* Skip over the IF token */

  getToken();

  /* Evaluate the boolean expression */

  expression(exprBoolean, NULL);

  /* Make sure that the boolean expression is followed by the THEN token */

  if (token !=  tTHEN)
    error (eTHEN);
  else
    {
      /* Skip over the THEN token */

      getToken();

      /* Generate a conditional branch to the "else_label."  This will be a
       * branch to either the ENDIF or to the ELSE location (if present).
       */

      pas_GenerateDataOperation(opJEQUZ, else_label);

      /* Save the value of the Level Stack Pointer (LSP) here.  This will be
       * the value of the LSP at the ENDIF label if there is no ELSE <statement>
       * presentl.  We will compare the elseLSP to the thenLSP at that point.
       */

      elseLSP = pas_GetCurrentStackLevel();

      /* Parse the <statment> following the THEN token */

      statement();

      /* Save the LSP after generating the THEN <statement>.  We will compare the
       * elseLSP to the thenLSP below.
       */

      thenLSP = pas_GetCurrentStackLevel();

      /* Check for optional ELSE <statement> */

      if (token == tELSE)
        {
          /* Change the ENDIF label.  Now instead of branching to
           * the ENDIF, the logic above will branch to the ELSE
           * logic generated here.
           */

          endif_label = ++label;

          /* Skip over the ELSE token */

          getToken();

          /* Generate Jump to ENDIF label after the  THEN <statement> */

          pas_GenerateDataOperation(opJMP, endif_label);

          /* Generate the ELSE label here.  This is where we will go if
           * the IF <expression> evaluates to false.
           */

          pas_GenerateDataOperation(opLABEL, else_label);

          /* Generate the ELSE <statement> then fall through to the
           * ENDIF label.
           */

          statement();

          /* Save the LSP after generating the ELSE <statement>.  We will
           * compare elseLSP to the thenLSP below.
           */

          elseLSP = pas_GetCurrentStackLevel();
        }

      /* Generate the ENDIF label here.  Note that if no ELSE <statement>
       * is present, this will be the same as the else_label.
       */

      pas_GenerateDataOperation(opLABEL, endif_label);

      /* We can get to this location through two of three pathes:  (1) through the
       * THEN <statement>, (2) from the IF <expression> if no ELSE <statement>
       * is present, or (3) from the ELSE <statement>.  If the LSP is different
       * through these two pathes, then we will have to invalidate it.
       */

      if (thenLSP != elseLSP)
        {
          pas_InvalidateCurrentStackLevel();
        }
    }
}

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

void compoundStatement(void)
{
   TRACE(lstFile,"[compoundStatement]");

   /* Process statements until END encountered */
   do
     {
       getToken();
       statement();
     }
   while (token == ';');

   /* Verify that it really was END */

   if (token != tEND) error (eEND);
   else getToken();
}

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

void pas_RepeatStatement ()
{
   uint16_t rpt_label = ++label;

   TRACE(lstFile,"[pas_RepeatStatement]");

   /* REPEAT <statement[;statement[statement...]]> UNTIL <expression> */

   /* Generate top of loop label */

   pas_GenerateDataOperation(opLABEL, rpt_label);
   do
     {
       getToken();

       /* Process <statement> */

       statement();
     }
   while (token == ';');

   /* Verify UNTIL follows */

   if (token !=  tUNTIL) error (eUNTIL);
   else getToken();

   /* Generate UNTIL <expression> */

   expression(exprBoolean, NULL);

   /* Generate conditional branch to the top of loop */

   pas_GenerateDataOperation(opJEQUZ, rpt_label);

   /* NOTE:  The current LSP setting will be correct after the repeat
    * loop because we fall through from the bottom of the loop after
    * executing the body at least once.
    */
}

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

static void pas_WhileStatement(void)
{
   uint16_t while_label    = ++label;     /* Top of loop label */
   uint16_t endwhile_label = ++label;     /* End of loop label */
   uint32_t nLspChanges;
   int32_t  topOfLoopLSP;
   bool     bCheckLSP      = false;

   TRACE(lstFile,"[pas_WhileStatement]");

   /* Generate WHILE <expression> DO <statement> */

   /* Skip over WHILE token */

   getToken();

   /* Set top of loop label */

   pas_GenerateDataOperation(opLABEL, while_label);

   /* Evaluate the WHILE <expression> */

   nLspChanges = pas_GetNStackLevelChanges();
   expression(exprBoolean, NULL);

   /* Generate a conditional jump to the end of the loop */

   pas_GenerateDataOperation(opJEQUZ, endwhile_label);

   /* Save the level stack pointer (LSP) at the top of the
    * loop.  When first executed, this value will depend on
    * logic prior to the loop or on values set in the
    * WHILE <expression>.  On subsequent loops, this value
    * may be determined by logic within the loop body or
    * have to restore this value when the loop terminates.
    */

   topOfLoopLSP =  pas_GetCurrentStackLevel();

   /* Does the WHILE <expression> logic set the LSP? */

   if (nLspChanges == pas_GetNStackLevelChanges())
     {
       /* Yes, then the value set in the WHILE <expression>
        * is the one that will be in effect at the end_while
        * label.
        */

       bCheckLSP = true;
     }

   /* Verify that the DO token follows the expression */

   if (token !=  tDO) error(eDO);
   else getToken();

   /* Generate the <statement> following the DO token */

   statement();

   /* Generate a branch to the top of the loop */

   pas_GenerateDataOperation(opJMP, while_label);

   /* Set the bottom of loop label */

   pas_GenerateDataOperation(opLABEL, endwhile_label);

   /* We always get here from the check at the top of the loop.
    * Normally this will be from the branch from the bottom of 
    * the loop to the top of the loop.  Then from the conditional
    * branch at the top of the loop to here.
    *
    * But, we need to allow for the special case when the body
    * of the while loop never executed.  The flag bCheckLSP is
    * set true if the conditional expression evaluation does not
    * set the LSP.  In the case, the current LSP will be either
    * the LSP at the top of the loop (if he body was never executed)
    * or the current LSP (the body executes at least once).
    */

   if (bCheckLSP)
     {
       if (topOfLoopLSP != pas_GetCurrentStackLevel())
         {
           /* In thise case, there is uncertainty in the value of the
            * LSP and we must invalidate it.  It will be reset to the
            * correct the next time that a level stack reference is
            * performed.
            */

           pas_InvalidateCurrentStackLevel();
         }
     }
   else
     {
       /* Otherwise, make sure that the code generation logic knows
        * the correct value of the LSP at this point.
        */

       pas_SetCurrentStackLevel(topOfLoopLSP);
     }
}

/***********************************************************************/
/* This is helper function for pas_CaseStatement */

static bool pas_CheckInvalidateLSP(int32_t *pTerminalLSP)
{
  /* Check the LSP after evaluating the case <statement>. */

  int32_t caseLSP = pas_GetCurrentStackLevel();
  if (caseLSP < 0)
    {
      /* If the LSP is invalid after any case <statement>, then it could
       * be invalid at the end_case label as well.
       */

      return true;
    }
  else if (*pTerminalLSP < 0)
    {
      /* The value of the LSP at the end_case label has not
       * yet been determined.  It must be the value at the
       * end of this case <statement> (or else it is invalid)
       */

      *pTerminalLSP = caseLSP;
    }
  else if (*pTerminalLSP != caseLSP)
    {
      /* The value of the LSP at the end of this case <statement> is
       * different from the value of the LSP at the end of some other
       * case <statement>.  The value of the LSP at the end_case label
       * will be indeterminate and must be invalidated.
       */

      return true;
    }
  /* So far so good */

  return false;
}

static void pas_CaseStatement(void)
{
   uint16_t this_case;
   uint16_t next_case      = ++label;
   uint16_t end_case       = ++label;
   int32_t  terminalLSP    = -1;
   bool     bInvalidateLSP = false;

   TRACE(lstFile,"[pas_CaseStatement]");

   /* Process "CASE <expression> OF" */

   /* Skip over the CASE token */

   getToken();

   /* Evaluate the CASE <expression> */

   expression(exprAnyOrdinal, NULL);

   /* Verify that CASE <expression> is followed with the OF token */

   if (token !=  tOF) error (eOF);
   else getToken();

   /* Loop to process each case until END encountered */

   for (;;)
     {
       this_case = next_case;
       next_case = ++label;

       /* Process NON-STANDARD ELSE <statement> END */

       if (token == tELSE)
         {
           getToken();

           /* Set ELSE statement label */

           pas_GenerateDataOperation(opLABEL, this_case);

           /* Evaluate ELSE statement */

           statement();

           /* Check the LSP after evaluating the ELSE <statement>. */

           if (pas_CheckInvalidateLSP(&terminalLSP))
             {
               /* The LSP will be invalid at the end case label.  Set
                * a flag so that we can handle invalidation of the LSP when
                * we get to the end case label.
                */

               bInvalidateLSP = true;
             }

           /* Verify that END follows the ELSE <statement> */

           if (token != tEND) error(eEND);
           else getToken();

           /* Terminate FOR loop */

           break;
         }

       /* Process "<constant>[,<constant>[,...]] : <statement>"
        * NOTE:  We accept any kind of constant for the case selector; there
        * really should be some check to assure that the constant is of the
        * same type as the expression!
        */  

       else
         {
           /* Loop for each <constant> in the case list */

           for(;;)
             {
               /* Verify that we have a constant */

               if (!isConstant(token))
                 {
                   error(eINTCONST);
                   break;
                 }

               /* Generate a comparison of the CASE expression and the constant.
                *
                * First duplicate the value to be compared (from the CASE <expression>)
                * and push the comparison value (from the <constant>:)
                */

               pas_GenerateSimple(opDUP);
               pas_GenerateDataOperation(opPUSH, tknInt);

               /* The kind of comparison we generate depends on if we have to
                * jump over other case selector comparsions to the statement
                * or if we can just fall through to the statement
                */

               /* Skip over the constant */

               getToken();

               /* If there are multiple constants, they will be separated with
                * commas.
                */

               if (token == ',')
                 {
                   /* Generate jump to <statement> */

                   pas_GenerateDataOperation(opJEQUZ, this_case);

                   /* Skip over comma */

                   getToken();
                 }
               else
                 {
                   /* else jump to the next case */

                   pas_GenerateDataOperation(opJNEQZ, next_case);
                   break;
                 }
             }

           /* Then process ... : <statement> */

           /* Verify colon presence */

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

           /* Set CASE label */

           pas_GenerateDataOperation(opLABEL, this_case);

           /* Evaluate <statement> */

           statement();

           /* Jump to exit CASE */

           pas_GenerateDataOperation(opJMP, end_case);

           /* Check the LSP after evaluating the case <statement>. */

           if (pas_CheckInvalidateLSP(&terminalLSP))
             {
               /* If the LSP will be invalid at the end case label.  Set
                * a flag so that we can handle invalidation of the LSP when
                * we get to the end case label.
                */

               bInvalidateLSP = true;
             }
         }

       /* Check if there are more statements.  If not, verify END present */

       if (token == ';')
         {
           getToken();
         }
       else if (token == tEND)
         {
           getToken();
           break;
         }
       else
         {
           error (eEND);
           break;
         }
     }

   /* Generate ENDCASE label and Pop CASE <expression> from stack */

   pas_GenerateDataOperation(opLABEL, end_case);
   pas_GenerateDataOperation(opINDS, -sINT_SIZE);

   /* We may have gotten to this point from many different case <statements>.
    * The flag bInvalidateLSP will be set if the LSP is not the same for
    * each of these pathes.  Invalidating the LSP will force it to be reloaded
    * when the next level stack access is done.
    */

   if (bInvalidateLSP)
     {
       pas_InvalidateCurrentStackLevel();
     }
}

/***********************************************************************/
static void pas_ForStatement(void)
{
   STYPE *varPtr;
   uint16_t forLabel    = ++label;
   uint16_t endForLabel = ++label;
   uint16_t jmpOp;
   uint16_t modOp;
   int32_t topOfLoopLSP;

   TRACE(lstFile,"[pas_ForStatement]");

   /* FOR <assigment statement> <TO, DOWNTO> <expression> DO <statement> */

   /* Skip over the FOR token */

   getToken();

   /* Get and verify the left side of the assignment. */
   if ((token != sINT) && (token != sSUBRANGE))
     error(eINTVAR);
   else
     {
       /* Save the token associated with the left side of the assignment
        * and evaluate the integer assignment.
        */

       varPtr = tknPtr;
       getToken();

       /* Generate the assignment to the integer variable */

       pas_Assignment(opSTS, exprInteger, tknPtr, tknPtr->sParm.v.parent);

       /* Determine if this is a TO or a DOWNTO loop and set up the opCodes
        * to generate appropriately.
        */

       if (token == tDOWNTO)
         {
           jmpOp = opJGT;
           modOp = opDEC;
           getToken();
         }
       else if (token == tTO)
         {
           jmpOp = opJLT;
           modOp = opINC;
           getToken();
         }
       else
         error (eTOorDOWNTO);

       /* Evaluate <expression> DO */

       expression(exprInteger, varPtr->sParm.v.parent);

       /* Verify that the <expression> is followed by the DO token */

       if (token != tDO) error (eDO);
       else getToken();

       /* Generate top of loop label */

       pas_GenerateDataOperation(opLABEL, forLabel);

       /* Generate the top of loop comparison.  Duplicate the end of loop
        * value, push the current value, and perform the comparison.
        */

       pas_GenerateSimple(opDUP);
       pas_GenerateStackReference(opLDS, varPtr);
       pas_GenerateDataOperation(jmpOp, endForLabel);

       /* Save the level stack pointer (LSP) at the top of the FOR
        * loop.  When first executed, this value will depend on
        * logic prior to the loop body. On subsequent loops, this
        * value may be determined by logic within the loop body.
        */

       topOfLoopLSP = pas_GetCurrentStackLevel();

       /* Evaluate the for statement <statement> */

       statement();

       /* Generate end of loop logic:  Load the variable, modify the
        * variable, store the variable, and jump unconditionally to the
        * top of the loop.
        */

       pas_GenerateStackReference(opLDS, varPtr);
       pas_GenerateSimple(modOp);
       pas_GenerateStackReference(opSTS, varPtr);
       pas_GenerateDataOperation(opJMP, forLabel);

       /* Generate the end of loop label.  This is where the conditional
        * branch at the top of the loop will come to.
        */

       pas_GenerateDataOperation(opLABEL, endForLabel);
       pas_GenerateDataOperation(opINDS, -sINT_SIZE);

       /* We always get here from the check at the top of the loop.
        * Normally this will be from the branch from the bottom of 
        * the loop to the top of the loop.  Then from the conditional
        * branch at the top of the loop to here.
        *
        * But, we need to allow for the special case when the body
        * of the for loop never executed.  In this case, the LSP at
        * the first time into the loop may differ from the LSP at
        * subsequent times into the loop.  If this is the case, then
        * will will have to invalidate the LSP.
        */

       if (topOfLoopLSP != pas_GetCurrentStackLevel())
         {
           /* In thise case, there is uncertainty in the value of the
            * LSP and we must invalidate it.  It will be reset to the
            * correct the next time that a level stack reference is
            * performed.
            */

           pas_InvalidateCurrentStackLevel();
         }
     }
}

/***********************************************************************/
static void pas_WithStatement(void)
{
   WTYPE saveWithRecord;

   TRACE(lstFile,"[pas_WithStatement]");

   /* Generate WITH <variable[,variable[...]] DO <statement> */

   /* Save the current WITH pointer.  Only one WITH can be active at
    * any given time.
    */

   saveWithRecord = withRecord;

   /* Process each RECORD or RECORD OBJECT in the <variable> list */

   getToken();
   for(;;)
     {
       /* A RECORD type variable may be used in the WITH statement only if
        * there is no other WITH active
        */

       if ((token == sRECORD) && (!withRecord.parent))
         {
           /* Save the RECORD variable as the new withRecord */

           withRecord.level   = tknPtr->sLevel;
           withRecord.pointer = false;
           withRecord.varParm = false;
           withRecord.offset  = tknPtr->sParm.v.offset;
           withRecord.parent  = tknPtr->sParm.v.parent;

           /* Skip over the RECORD variable */

           getToken();
         }

       /* A RECORD VAR parameter may also be used in the WITH statement
        * (again only if there is no other WITH active)
        */

       else if ((token == sVAR_PARM) &&
                (!withRecord.parent) &&
                (tknPtr->sParm.v.parent->sParm.t.type == sRECORD))
         {
           /* Save the RECORD VAR parameter as the new withRecord */

           withRecord.level   = tknPtr->sLevel;
           withRecord.pointer = true;
           withRecord.varParm = true;
           withRecord.offset  = tknPtr->sParm.v.offset;
           withRecord.parent  = tknPtr->sParm.v.parent;

           /* Skip over the RECORD VAR parameter */

           getToken();
         }

       /* A pointer to a RECORD may also be used in the WITH statement
        * (again only if there is no other WITH active)
        */

       else if ((token == sPOINTER) &&
                (!withRecord.parent) &&
                (tknPtr->sParm.v.parent->sParm.t.type == sRECORD))
         {
           /* Save the RECORD pointer as the new withRecord */

           withRecord.level   = tknPtr->sLevel;
           withRecord.pointer = true;
           withRecord.pointer = false;
           withRecord.offset  = tknPtr->sParm.v.offset;
           withRecord.parent  = tknPtr->sParm.v.parent;

           /* Skip over the RECORD pointer */

           getToken();

           /* Verify that deferencing is specified! */

           if (token != '^') error(eRECORDVAR);
           else getToken();
         }

       /* A RECORD_OBJECT may be used in the WITH statement if the field
        * is from the same sRECORD type and is itself of type RECORD.
        */

       else if ((token == sRECORD_OBJECT) &&
                (tknPtr->sParm.r.record == withRecord.parent) &&
                (tknPtr->sParm.r.parent->sParm.t.type == sRECORD))
         {
           /* Okay, update the withRecord to use this record field */

           if (withRecord.pointer)
             withRecord.index += tknPtr->sParm.r.offset;
           else
             withRecord.offset += tknPtr->sParm.r.offset;

           withRecord.parent  = tknPtr->sParm.r.parent;

           /* Skip over the sRECORD_OBJECT */

           getToken();
     }

     /* Anything else is an error */

       else
         {
           error(eRECORDVAR);
           break;
         }


       /* Check if there are multiple variables in the WITH statement */

       if (token == ',') getToken();
       else break;
     }

   /* Verify that the RECORD list is terminated with DO */

   if (token != tDO) error (eDO);
   else getToken();

   /* Then process the statement following the WITH */

   statement();

   /* Restore the previous value of the withRecord */

   withRecord = saveWithRecord;
}

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

