/***************************************************************
  
        bwb_cnd.c       Conditional Expressions and Commands
                        for Bywater BASIC Interpreter
  
                        Copyright (c) 1993, Ted A. Campbell
                        Bywater Software
  
                        email: tcamp@delphi.com
  
        Copyright and Permissions Information:
  
        All U.S. and international rights are claimed by the author,
        Ted A. Campbell.
  
   This software is released under the terms of the GNU General
   Public License (GPL), which is distributed with this software
   in the file "COPYING".  The GPL specifies the terms under
   which users may copy and use the software in this distribution.
  
   A separate license is available for commercial distribution,
   for information on which you should contact the author.
  
***************************************************************/

/*---------------------------------------------------------------*/
/* NOTE: Modifications marked "JBV" were made by Jon B. Volkoff, */
/* 11/1995 (eidetics@cerf.net).                                  */
/*                                                               */
/* Those additionally marked with "DD" were at the suggestion of */
/* Dale DePriest (daled@cadence.com).                            */
/*                                                               */
/* Version 3.00 by Howard Wulf, AF5NE                            */
/*                                                               */
/* Version 3.10 by Howard Wulf, AF5NE                            */
/*                                                               */
/* Version 3.20 by Howard Wulf, AF5NE                            */
/*                                                               */
/*---------------------------------------------------------------*/



#include "bwbasic.h"

static LineType *bwb_then_else (LineType * l, int Value);
static LineType *bwb_if_file (LineType * l, int ThenValue);
static int FindTopLineOnStack (LineType * l);
static int for_limit_check (DoubleType Value, DoubleType Target,
                            DoubleType Step);
static int IsTypeMismatch (char LeftTypeCode, char RightTypeCode);


/* 
--------------------------------------------------------------------------------------------
                               EXIT
--------------------------------------------------------------------------------------------
*/

LineType *
bwb_EXIT (LineType * l)
{
   
  assert (l != NULL);
  WARN_SYNTAX_ERROR;
  return (l);
}

/* 
--------------------------------------------------------------------------------------------
                               SELECT
--------------------------------------------------------------------------------------------
*/
LineType *
bwb_SELECT (LineType * l)
{
   
  assert (l != NULL);
  WARN_SYNTAX_ERROR;
  return (l);
}


/* 
--------------------------------------------------------------------------------------------
                               FUNCTION - END FUNCTION
--------------------------------------------------------------------------------------------
*/

/***************************************************************
  
        FUNCTION:       bwb_FUNCTION()
  
   DESCRIPTION:    This function implements the BASIC
         FUNCTION command, introducing a named
         function.
  
   SYNTAX: FUNCTION subroutine-name
              ...
              [ EXIT FUNCTION ]
              ...
           END FUNCTION
  
***************************************************************/

LineType *
bwb_FUNCTION (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* check current exec level */
  assert(My != NULL);
  assert(My->StackHead != NULL);
  if (My->StackHead->next == NULL)
  {
    /* skip over the entire function definition */
    l = l->OtherLine;                /* line of END SUB */
    l = l->next;                /* line after END SUB */
    l->position = 0;
    return l;
  }

  /* we are being executed via IntrinsicFunction_deffn() */

  /* if this is the first time at this SUB statement, note it */
  if (My->StackHead->LoopTopLine != l)
  {
    if (bwb_incexec ())
    {
      /* OK */
      My->StackHead->LoopTopLine = l;
    }
    else
    {
      /* ERROR */
      WARN_OUT_OF_MEMORY;
      return My->EndMarker;
    }
  }
  line_skip_eol (l);
  return (l);
}


LineType *
bwb_EXIT_FUNCTION (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* check integrity of SUB commmand */
  if (FindTopLineOnStack (l->OtherLine))
  {
    /* FOUND */
    LineType *r;
    bwb_decexec ();
    r = l->OtherLine;                /* line of FUNCTION */
    r = r->OtherLine;                /* line of END FUNCTION */
    r = r->next;                /* line after END FUNCTION */
    r->position = 0;
    return r;
  }
  /* NOT FOUND */
  WARN_EXIT_FUNCTION_WITHOUT_FUNCTION;
  return (l);
}

LineType *
bwb_END_FUNCTION (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* check integrity of SUB commmand */
  if (FindTopLineOnStack (l->OtherLine) == FALSE)
  {
    /* NOT FOUND */
    WARN_END_FUNCTION_WITHOUT_FUNCTION;
    return (l);
  }
  /* decrement the stack */
  bwb_decexec ();

  /* and return next from old line */
  assert(My != NULL);
  assert(My->StackHead != NULL);
  My->StackHead->line->next->position = 0;
  return My->StackHead->line->next;
}

LineType *
bwb_FNEND (LineType * l)
{
   
  assert (l != NULL);
  return bwb_END_FUNCTION (l);
}

LineType *
bwb_FEND (LineType * l)
{
   
  assert (l != NULL);
  return bwb_END_FUNCTION (l);
}


/* 
--------------------------------------------------------------------------------------------
                               SUB - END SUB
--------------------------------------------------------------------------------------------
*/

/***************************************************************
  
        FUNCTION:       bwb_sub()
  
   DESCRIPTION:    This function implements the BASIC
         SUB command, introducing a named
         subroutine.
  
   SYNTAX: SUB subroutine-name
              ...
              [ EXIT SUB ]
              ...
           END SUB
  
***************************************************************/

LineType *
bwb_SUB (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* check current exec level */
  assert(My != NULL);
  assert(My->StackHead != NULL);
  if (My->StackHead->next == NULL)
  {
    /* skip over the entire function definition */
    l = l->OtherLine;                /* line of END SUB */
    l = l->next;                /* line after END SUB */
    l->position = 0;
    return l;
  }
  /* we are being executed via IntrinsicFunction_deffn() */

  /* if this is the first time at this SUB statement, note it */
  if (My->StackHead->LoopTopLine != l)
  {
    if (bwb_incexec ())
    {
      /* OK */
      My->StackHead->LoopTopLine = l;
    }
    else
    {
      /* ERROR */
      WARN_OUT_OF_MEMORY;
      return My->EndMarker;
    }
  }
  line_skip_eol (l);
  return (l);
}

LineType *
bwb_EXIT_SUB (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* check integrity of SUB commmand */
  if (FindTopLineOnStack (l->OtherLine))
  {
    /* FOUND */
    LineType *r;
    bwb_decexec ();
    r = l->OtherLine;                /* line of FUNCTION */
    r = r->OtherLine;                /* line of END FUNCTION */
    r = r->next;                /* line after END FUNCTION */
    r->position = 0;
    return r;
  }
  /* NOT FOUND */
  WARN_EXIT_SUB_WITHOUT_SUB;
  return (l);
}

LineType *
bwb_SUBEXIT (LineType * l)
{
   
  assert (l != NULL);
  return bwb_EXIT_SUB (l);
}

LineType *
bwb_SUB_EXIT (LineType * l)
{
   
  assert (l != NULL);
  return bwb_EXIT_SUB (l);
}

LineType *
bwb_END_SUB (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* check integrity of SUB commmand */
  if (FindTopLineOnStack (l->OtherLine) == FALSE)
  {
    /* NOT FOUND */
    WARN_END_SUB_WITHOUT_SUB;
    return (l);
  }
  /* decrement the stack */
  bwb_decexec ();

  /* and return next from old line */
  assert(My != NULL);
  assert(My->StackHead != NULL);
  My->StackHead->line->next->position = 0;
  return My->StackHead->line->next;
}

LineType *
bwb_SUBEND (LineType * l)
{
   
  assert (l != NULL);
  return bwb_END_SUB (l);
}

LineType *
bwb_SUB_END (LineType * l)
{
   
  assert (l != NULL);
  return bwb_END_SUB (l);
}


/* 
--------------------------------------------------------------------------------------------
                                IF - END IF
--------------------------------------------------------------------------------------------
*/


/***************************************************************
  
        FUNCTION:       bwb_IF()
  
        DESCRIPTION:    This function handles the BASIC IF
                        statement, standard flavor.
  standard
      SYNTAX:     IF expression  THEN line [ELSE line]
                  IF END  # file THEN line [ELSE line]
                  IF MORE # file THEN line [ELSE line]
  
***************************************************************/
LineType *
bwb_IF (LineType * l)
{
  /* classic IF */
  /* IF expression THEN 100          */
  /* IF expression THEN 100 ELSE 200 */
  int Value;
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  if (line_read_integer_expression (l, &Value) == FALSE)
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }
  return bwb_then_else (l, Value);
}

LineType *
bwb_IF_END (LineType * l)
{
  /* IF END #1 THEN 100          */
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  assert(My != NULL);
  assert(My->CurrentVersion != NULL);
  if (My->CurrentVersion->OptionVersionValue & (C77))
  {
    /* sets a linenumber to branch to on EOF */
    int FileNumber = 0;
    int LineNumber = 0;


    if (line_read_integer_expression (l, &FileNumber) == FALSE)
    {
      WARN_BAD_FILE_NUMBER;
      return (l);
    }
    if (FileNumber <= 0)
    {
      WARN_BAD_FILE_NUMBER;
      return (l);
    }
    if (line_skip_word (l, "THEN") == FALSE)
    {
      WARN_SYNTAX_ERROR;
      return (l);
    }
    if (line_read_integer_expression (l, &LineNumber) == FALSE)
    {
      WARN_UNDEFINED_LINE;
      return (l);
    }
    if (LineNumber < 0)
    {
      WARN_UNDEFINED_LINE;
      return (l);
    }
    /* now, we are ready to create the file */
    My->CurrentFile = find_file_by_number (FileNumber);
    if (My->CurrentFile == NULL)
    {
      My->CurrentFile = file_new ();
      My->CurrentFile->FileNumber = FileNumber;
    }
    My->CurrentFile->EOF_LineNumber = LineNumber;
    return (l);
  }
  /* branch to the line if we are currently at EOF */
  return bwb_if_file (l, TRUE);
}

LineType *
bwb_IF_MORE (LineType * l)
{
  /* IF MORE #1 THEN 100          */
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }
  /* branch to the line if we are not currently at EOF */
  return bwb_if_file (l, FALSE);
}





/***************************************************************
  
        FUNCTION:       bwb_IF8THEN()
  
        DESCRIPTION:    This function handles the BASIC IF
                        statement, structured flavor.
  
      SYNTAX:     IF expression THEN
                     ...
                  ELSEIF expression
                     ...
                  ELSE
                     ...
                  END IF
  
***************************************************************/
LineType *
bwb_IF8THEN (LineType * l)
{
  /* structured IF */
  LineType *else_line;
  int Value;
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* evaluate the expression */
  if (line_read_integer_expression (l, &Value) == FALSE)
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }
  if (Value)
  {
    /* expression is TRUE */
    l->next->position = 0;
    return l->next;
  }

  /* 
     RESUME knows we iterate thru the various ELSEIF commands, and restarts at the IF THEN command.
     RESUME NEXT knows we iterate thru the various ELSEIF commands, and restarts at the END IF command.
   */

  for (else_line = l->OtherLine; else_line->cmdnum == C_ELSEIF;
       else_line = else_line->OtherLine)
  {
    else_line->position = else_line->Startpos;

    /* evaluate the expression */
    if (line_read_integer_expression (else_line, &Value) == FALSE)
    {
      WARN_SYNTAX_ERROR;
      return (l);
    }
    if (Value)
    {
      /* expression is TRUE */
      else_line->next->position = 0;
      return else_line->next;
    }
  }
  /* ELSE or END IF */
  else_line->next->position = 0;
  return else_line->next;
}

LineType *
bwb_ELSEIF (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  for (l = l->OtherLine; l->OtherLine != NULL; l = l->OtherLine);
  l = l->next;                        /* line after END IF */
  l->position = 0;
  return l;
}

LineType *
bwb_ELSE (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  for (l = l->OtherLine; l->OtherLine != NULL; l = l->OtherLine);
  l = l->next;                        /* line after END IF */
  l->position = 0;
  return l;
}

LineType *
bwb_END_IF (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }
  return (l);
}

/* 
--------------------------------------------------------------------------------------------
                                 SELECT CASE - END SELECT
--------------------------------------------------------------------------------------------
*/


/***************************************************************
  
        FUNCTION:       bwb_select()
  
        DESCRIPTION:    This C function handles the BASIC SELECT
                        statement.
  
   SYNTAX:     SELECT CASE expression           ' examples:
               CASE value                       ' CASE 5
               CASE min TO max                  ' CASE 1 TO 10
               CASE IF relationaloperator value ' CASE IF > 5
               CASE IS relationaloperator value ' CASE IS > 5
               CASE ELSE
               END SELECT
  
***************************************************************/



LineType *
bwb_SELECT_CASE (LineType * l)
{
  VariantType selectvalue;
  VariantType *e;
  LineType *else_line;
   
  assert (l != NULL);

  e = &selectvalue;
  CLEAR_VARIANT (e);
  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* evaluate the expression */
  if (line_read_expression (l, e) == FALSE)        /* bwb_SELECT_CASE */
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }
  /* 
   **
   ** RESUME knows we iterate thru the various CASE commands, and restarts at the SELECT CASE command.
   ** RESUME NEXT knows we iterate thru the various CASE commands, and restarts at the END SELECT command.
   **
   */
  for (else_line = l->OtherLine; else_line->cmdnum == C_CASE;
       else_line = else_line->OtherLine)
  {
    else_line->position = else_line->Startpos;
    do
    {
      /* evaluate the expression */
      if (line_skip_word (else_line, "IF")
          || line_skip_word (else_line, "IS"))
      {
        /* CASE IS < 10    */
        /* CASE IF < "DEF" */
        /* CASE IS > 7     */
        /* CASE IS > "ABC" */
        char *tbuf;
        int tlen;
        size_t n;                /* number of characters we want to put in tbuf */
        int position;
        VariantType casevalue;
        VariantType *r;

        assert(My != NULL);
        assert(My->ConsoleOutput != NULL);
        assert(MAX_LINE_LENGTH > 1);
        tbuf = My->ConsoleOutput;
        tlen = MAX_LINE_LENGTH;
        n = 0;
        r = &casevalue;
        CLEAR_VARIANT (r);

        /*
         ** 
         ** Available choices:
         ** 1.  Parse every possible operator combination, depending upon the BASIC flavor.
         ** 2.  Jump into the middle of the expression parser, by exposing the parser internals.
         ** 3.  Limit the length of the expression.  This is the choice I made.
         **
         */

        if (e->VariantTypeCode == StringTypeCode)
        {
          /* STRING */
          n += bwb_strlen (e->Buffer);
          if (n > tlen)
          {
            WARN_STRING_FORMULA_TOO_COMPLEX;        /* bwb_SELECT_CASE */
            return (l);
          }
          /* OK , everything will fit */
          bwb_strcpy (tbuf, e->Buffer);
        }
        else
        {
          /* NUMBER */
          FormatBasicNumber (e->Number, tbuf);
          n += bwb_strlen (tbuf);
          if (n > tlen)
          {
            WARN_STRING_FORMULA_TOO_COMPLEX;        /* bwb_SELECT_CASE */
            return (l);
          }
          /* OK , everything will fit */
        }
        {
          char *Space;

          Space = " ";
          n += bwb_strlen (Space);
          if (n > tlen)
          {
            WARN_STRING_FORMULA_TOO_COMPLEX;        /* bwb_SELECT_CASE */
            return (l);
          }
          /* OK , everything will fit */
          bwb_strcat (tbuf, Space);
        }
        {
          n += bwb_strlen (&(else_line->buffer[else_line->position]));

          if (n > tlen)
          {
            WARN_STRING_FORMULA_TOO_COMPLEX;        /* bwb_SELECT_CASE */
            return (l);
          }
          /* OK , everything will fit */
          bwb_strcat (tbuf, &(else_line->buffer[else_line->position]));
        }
        position = 0;
        if (buff_read_expression (tbuf, &position, r) == FALSE)        /* bwb_SELECT_CASE */
        {
          WARN_SYNTAX_ERROR;
          return (l);
        }
        if (r->VariantTypeCode == StringTypeCode)
        {
          RELEASE_VARIANT (r);
          WARN_TYPE_MISMATCH;
          return (l);
        }
        if (r->Number)
        {
          /* expression is TRUE */
          else_line->next->position = 0;
          return else_line->next;
        }
        /* condition is FALSE */
        /* proceed to next CASE line if there is one */
      }
      else
      {
        /* CASE 7 */
        /* CASE 7 TO 10 */
        /* CASE "ABC" */
        /* CASE "ABC" TO "DEF" */
        VariantType minvalue;
        VariantType *minval;

        minval = &minvalue;
        CLEAR_VARIANT (minval);
        /* evaluate the MIN expression */
        if (line_read_expression (else_line, minval) == FALSE)        /* bwb_SELECT_CASE */
        {
          WARN_SYNTAX_ERROR;
          return (l);
        }
        if (IsTypeMismatch (e->VariantTypeCode, minval->VariantTypeCode))
        {
          RELEASE_VARIANT (minval);
          WARN_TYPE_MISMATCH;
          return (l);
        }
        if (line_skip_word (else_line, "TO"))
        {
          /* CASE 7 TO 10 */
          /* CASE "ABC" TO "DEF" */
          VariantType maxvalue;
          VariantType *maxval;

          maxval = &maxvalue;
          CLEAR_VARIANT (maxval);

          /* evaluate the MAX expression */
          if (line_read_expression (else_line, maxval) == FALSE)        /* bwb_SELECT_CASE */
          {
            WARN_SYNTAX_ERROR;
            return (l);
          }
          if (IsTypeMismatch (e->VariantTypeCode, maxval->VariantTypeCode))
          {
            RELEASE_VARIANT (maxval);
            WARN_TYPE_MISMATCH;
            return (l);
          }
          if (e->VariantTypeCode == StringTypeCode)
          {
            /* STRING */
            if (bwb_strcmp (e->Buffer, minval->Buffer) >= 0
                && bwb_strcmp (e->Buffer, maxval->Buffer) <= 0)
            {
              /* expression is TRUE */
              RELEASE_VARIANT (maxval);
              else_line->next->position = 0;
              return else_line->next;
            }
            RELEASE_VARIANT (maxval);
          }
          else
          {
            /* NUMBER */
            if (e->Number >= minval->Number && e->Number <= maxval->Number)
            {
              /* expression is TRUE */
              else_line->next->position = 0;
              return else_line->next;
            }
          }
        }
        else
        {
          /* CASE 7 */
          /* CASE "ABC" */
          if (e->VariantTypeCode == StringTypeCode)
          {
            /* STRING */
            if (bwb_strcmp (e->Buffer, minval->Buffer) == 0)
            {
              /* expression is TRUE */
              RELEASE_VARIANT (minval);
              else_line->next->position = 0;
              return else_line->next;
            }
            RELEASE_VARIANT (minval);
          }
          else
          {
            /* NUMBER */
            if (e->Number == minval->Number)
            {
              /* expression is TRUE */
              else_line->next->position = 0;
              return else_line->next;
            }
          }
        }
        /* condition is FALSE */
        /* proceed to next CASE line if there is one */
      }
    }
    while (line_skip_seperator (else_line));
  }
  /* CASE_ELSE or END_SELECT */
  RELEASE_VARIANT (e);
  else_line->next->position = 0;
  return else_line->next;
}

LineType *
bwb_CASE (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  for (l = l->OtherLine; l->OtherLine != NULL; l = l->OtherLine);
  l = l->next;                        /* line after END SELECT */
  l->position = 0;
  return l;
}

LineType *
bwb_CASE_ELSE (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  for (l = l->OtherLine; l->OtherLine != NULL; l = l->OtherLine);
  l = l->next;                        /* line after END SELECT */
  l->position = 0;
  return l;
}


LineType *
bwb_END_SELECT (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  return (l);
}

/* 
--------------------------------------------------------------------------------------------
                                DO - LOOP
--------------------------------------------------------------------------------------------
*/

/***************************************************************
  
   FUNCTION:       bwb_DO()
  
   DESCRIPTION:    This C function implements the ANSI BASIC
         DO statement.
  
   SYNTAX:     DO [UNTIL|WHILE condition]
                  ...
                  [EXIT DO]
                  ...
               LOOP [UNTIL|WHILE condition]
  
***************************************************************/

LineType *
bwb_DO (LineType * l)
{
  LineType *r;
  int Value;
   
  assert (l != NULL);

  /* DO ' forever */
  /* DO UNTIL ' exits when != 0 */
  /* DO WHILE ' exits when == 0 */

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  do
  {
    /* evaluate the expression */
    if (line_is_eol (l))
    {
      break;                        /* exit 'do' */
    }
    else if (line_skip_word (l, "UNTIL"))
    {
      /* DO UNTIL */
      if (line_read_integer_expression (l, &Value) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return (l);
      }

      if (Value != 0)
      {
        /* EXIT DO */
        r = l->OtherLine;        /* line of LOOP */
        r = r->next;                /* line after LOOP */
        r->position = 0;
        return r;
      }
    }
    else if (line_skip_word (l, "WHILE"))
    {
      /* DO WHILE */
      if (line_read_integer_expression (l, &Value) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return (l);
      }

      if (Value == 0)
      {
        /* EXIT DO */
        r = l->OtherLine;        /* line of LOOP */
        r = r->next;                /* line after LOOP */
        r->position = 0;
        return r;
      }
    }

  }
  while (line_skip_seperator (l));

  return (l);
}


LineType *
bwb_EXIT_DO (LineType * l)
{
  LineType *r;
   
  assert (l != NULL);

  /* EXIT DO */

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  r = l->OtherLine;                /* line of DO */
  r = r->OtherLine;                /* line of LOOP */
  r = r->next;                        /* line after LOOP */
  r->position = 0;
  return r;
}


LineType *
bwb_LOOP (LineType * l)
{
  LineType *r;
  int Value;
   
  assert (l != NULL);

  /* LOOP ' forever */
  /* LOOP UNTIL ' exits when != 0 */
  /* LOOP WHILE ' exits when == 0 */

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  do
  {
    /* evaluate the expression */
    if (line_is_eol (l))
    {
      break;                        /* exit 'do' */
    }
    else if (line_skip_word (l, "UNTIL"))
    {
      /* LOOP UNTIL */
      if (line_read_integer_expression (l, &Value) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return (l);
      }

      if (Value != 0)
      {
        /* EXIT DO */
        return (l);
      }
    }
    else if (line_skip_word (l, "WHILE"))
    {
      /* LOOP WHILE */
      if (line_read_integer_expression (l, &Value) == FALSE)
      {
        WARN_SYNTAX_ERROR;
        return (l);
      }

      if (Value == 0)
      {
        /* EXIT DO */
        return (l);
      }
    }

  }
  while (line_skip_seperator (l));

  /* loop around to DO again */
  r = l->OtherLine;                /* line of DO */
  r->position = 0;
  return r;
}



/* 
--------------------------------------------------------------------------------------------
                                WHILE - WEND
--------------------------------------------------------------------------------------------
*/




/***************************************************************
  
        FUNCTION:       bwb_WHILE()
  
        DESCRIPTION:    This function handles the BASIC
                        WHILE statement.
  
   SYNTAX:     WHILE expression ' exits when == 0
                 ...
                 [EXIT WHILE]
                 ...
               WEND
  
  
***************************************************************/
LineType *
bwb_WHILE (LineType * l)
{
  int Value;

  LineType *r;
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  if (line_read_integer_expression (l, &Value) == FALSE)
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }
  if (Value == 0)
  {
    /* EXIT WHILE */
    r = l->OtherLine;                /* line of WEND */
    r = r->next;                /* line after WEND */
    r->position = 0;
    return r;
  }
  return (l);
}


LineType *
bwb_EXIT_WHILE (LineType * l)
{
  LineType *r;
   
  assert (l != NULL);

  /* EXIT WHILE */

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  r = l->OtherLine;                /* line of WHILE */
  r = r->OtherLine;                /* line of WEND */
  r = r->next;                        /* line after WEND */
  r->position = 0;
  return r;
}

LineType *
bwb_WEND (LineType * l)
{
  LineType *r;
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  r = l->OtherLine;                /* line of WHILE */
  r->position = 0;
  return r;
}


/* 
--------------------------------------------------------------------------------------------
                                REPEAT - UNTIL
--------------------------------------------------------------------------------------------
*/







/***************************************************************
  
        FUNCTION:       bwb_UNTIL()
  
        DESCRIPTION:    This function handles the BASIC 
                         UNTIL statement.
  
   SYNTAX:     UNTIL expression ' exits when != 0
                 ...
                 [EXIT UNTIL]
                 ...
               UEND
                       
  
***************************************************************/
LineType *
bwb_REPEAT (LineType * l)
{
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  return (l);
}


LineType *
bwb_EXIT_REPEAT (LineType * l)
{
  LineType *r;
   
  assert (l != NULL);

  /* EXIT REPEAT */

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  r = l->OtherLine;                /* line of REPEAT */
  r = r->OtherLine;                /* line of UNTIL */
  r = r->next;                        /* line after UNTIL */
  r->position = 0;
  return r;
}


LineType *
bwb_UNTIL (LineType * l)
{
  int Value;
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  if (line_read_integer_expression (l, &Value) == FALSE)
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }

  if (Value == 0)
  {
    /* GOTO REPEAT */
    LineType *r;

    r = l->OtherLine;                /* line of REPEAT */
    r->position = 0;
    return r;
  }
  /* EXITS when Value != 0 */
  return (l);

}


/* 
--------------------------------------------------------------------------------------------
                                FOR - NEXT
--------------------------------------------------------------------------------------------
*/



/***************************************************************
  
        FUNCTION:       bwb_for()
  
        DESCRIPTION:    This function handles the BASIC FOR
                        statement.
  
   SYNTAX:     FOR counter = start TO finish [STEP increment]
                 ...
                 [EXIT FOR]
                 ...
               NEXT [counter]
  
NOTE:    This is controlled by the OptionVersion bitmask.
  
    The order of expression evaluation and variable creation varies.
    For example:
        FUNCTION FNA( Y )
            PRINT "Y="; Y
            FNA = Y
        END FUNCTION
        FOR X = FNA(3) TO FNA(1) STEP FNA(2)
        NEXT X
    ANSI/ECMA;
        Y= 1
        Y= 2
        Y= 3
        X is created (if it does not exist)
        X is assigned the value of 3
    MICROSOFT;
        X is created (if it does not exist)
        Y= 3
        X is assigned the value of 3
        Y= 1
        Y= 2
  
  
ECMA-55: Section 13.4
       ...
       The action of the for-statement and the next-statement is de-
       fined in terms of other statements, as follows:
  
              FOR v = initial-value TO limit STEP increment
              (block)
              NEXT v
  
       is equivalent to:
  
              LET own1 = limit
              LET own2 = increment
              LET v = initial-value
       line1  IF (v-own1) * SGN (own2) > 0 THEN line2
              (block)
              LET v = v + own2
              GOTO line1
       line2  REM continued in sequence
       ...
  
***************************************************************/


LineType *
bwb_FOR (LineType * l)
{
  LineType *r;
  VariableType *v;
  DoubleType Value;
  DoubleType Target;
  DoubleType Step;
  VariantType variant;
  CLEAR_VARIANT (&variant);
   
  assert (l != NULL);
  assert(My != NULL);
  assert(My->CurrentVersion != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  /* if this is the first time at this FOR statement, note it */
  if (FindTopLineOnStack (l) == FALSE)
  {
    if (bwb_incexec ())
    {
      /* OK */
    }
    else
    {
      /* ERROR */
      WARN_OUT_OF_MEMORY;
      return My->EndMarker;
    }
  }

  /* INITIALIZE */


  if ((v = line_read_scalar (l)) == NULL)
  {
    WARN_VARIABLE_NOT_DECLARED;
    return (l);
  }
  if (v->dimensions > 0)
  {
    WARN_TYPE_MISMATCH;
    return (l);
  }
  if (v->VariableTypeCode == StringTypeCode)
  {
    WARN_TYPE_MISMATCH;
    return (l);
  }
  if (line_skip_EqualChar (l) == FALSE)
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }
  if (line_read_numeric_expression (l, &Value) == FALSE)
  {
    WARN_ILLEGAL_FUNCTION_CALL;
    return (l);
  }
  if (My->CurrentVersion->OptionFlags & OPTION_BUGS_ON /* FOR X = ... */ )
  {
    /* Assign Variable */
    variant.VariantTypeCode = v->VariableTypeCode;
    variant.Number = Value;
    if (var_set (v, &variant) == FALSE)
    {
      WARN_VARIABLE_NOT_DECLARED;
      return (l);
    }
  }
  else
  {
    /* assigned below */
  }
  if (line_skip_word (l, "TO") == FALSE)
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }
  if (line_read_numeric_expression (l, &Target) == FALSE)
  {
    WARN_ILLEGAL_FUNCTION_CALL;
    return (l);
  }
  if (line_skip_word (l, "STEP"))
  {
    if (line_read_numeric_expression (l, &Step) == FALSE)
    {
      WARN_ILLEGAL_FUNCTION_CALL;
      return (l);
    }
  }
  else
  {
    Step = 1;
  }
  if (My->CurrentVersion->OptionFlags & OPTION_BUGS_ON /* FOR X = ... */ )
  {
    /* assigned above */
  }
  else
  {
    /* Assign Variable */
    variant.VariantTypeCode = v->VariableTypeCode;
    variant.Number = Value;
    if (var_set (v, &variant) == FALSE)
    {
      WARN_VARIABLE_NOT_DECLARED;
      return (l);
    }
  }

  /* CHECK */
  if (for_limit_check (Value, Target, Step))
  {
    /* EXIT FOR */
    bwb_decexec ();

    r = l->OtherLine;                /* line of NEXT */
    r = r->next;                /* line after NEXT */
    r->position = 0;
    return r;
  }

  /* we will loop at least once */
  assert(My->StackHead != NULL);
  My->StackHead->line = l;
  My->StackHead->ExecCode = EXEC_FOR;
  My->StackHead->local_variable = v;
  My->StackHead->for_step = Step;
  My->StackHead->for_target = Target;
  My->StackHead->LoopTopLine = l;
  My->StackHead->OnErrorGoto = 0;
  /* proceed with processing */
  return (l);
}


LineType *
bwb_EXIT_FOR (LineType * l)
{
  LineType *r;
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  if (FindTopLineOnStack (l->OtherLine) == FALSE)
  {
    WARN_EXIT_FOR_WITHOUT_FOR;
    return (l);
  }
  assert(My != NULL);
  assert(My->StackHead != NULL);
  My->StackHead->ExecCode = EXEC_FOR;
  bwb_decexec ();

  r = l->OtherLine;                /* line of FOR */
  r = r->OtherLine;                /* line of NEXT */
  r = r->next;                        /* line after NEXT */
  r->position = 0;
  return r;
}


LineType *
bwb_NEXT (LineType * l)
{
  LineType *r;
  VariableType *v;
  DoubleType Value;
  DoubleType Target;
  DoubleType Step;
   
  assert (l != NULL);

  if (l->LineFlags & (LINE_USER))
  {
    WARN_ILLEGAL_DIRECT;
    return (l);
  }

  if (FindTopLineOnStack (l->OtherLine) == FALSE)
  {
    WARN_NEXT_WITHOUT_FOR;
    return (l);
  }
  assert(My != NULL);
  assert(My->StackHead != NULL);
  My->StackHead->ExecCode = EXEC_FOR;

  /* INCREMENT */
  v = My->StackHead->local_variable;
  Target = My->StackHead->for_target;
  Step = My->StackHead->for_step;

  /* if( TRUE ) */
  {
    VariantType variant;
    CLEAR_VARIANT (&variant);
    if (var_get (v, &variant) == FALSE)
    {
      WARN_NEXT_WITHOUT_FOR;
      return (l);
    }
    if (variant.VariantTypeCode == StringTypeCode)
    {
      WARN_NEXT_WITHOUT_FOR;
      return (l);
    }
    variant.Number += Step;
    Value = variant.Number;
    if (var_set (v, &variant) == FALSE)
    {
      WARN_NEXT_WITHOUT_FOR;
      return (l);
    }
  }

  /* CHECK */
  if (for_limit_check (Value, Target, Step))
  {
    /* EXIT FOR */
    bwb_decexec ();
    return (l);
  }
  /* proceed with processing */
  r = l->OtherLine;                /* line of FOR */
#if FALSE                        /* keep this ... */
  /* 
     This example causes a Syntax Error:
     100 FOR I = 1 TO 1000:NEXT
     The error is actually caused by execline().
     Note that the example is a delay loop.
     Only NEXT has this issue, because it jumps to TOP->next.
     All other loop structures jump to either TOP or BOTTOM->next.
   */
  r = r->next;                        /* line after FOR */
  r->position = 0;
#endif
  line_skip_eol (r);
  return r;
}


/* 
--------------------------------------------------------------------------------------------
                                STATIC UTILITY ROUTINES
--------------------------------------------------------------------------------------------
*/


static int
FindTopLineOnStack (LineType * l)
{
  /* since we are at the top of a loop, we MIGHT be on the stack */
  StackType *StackItem;
   
  assert (l != NULL);
  assert(My != NULL);

  for (StackItem = My->StackHead; StackItem != NULL;
       StackItem = StackItem->next)
  {
    LineType *current;

    current = StackItem->LoopTopLine;
    if (current != NULL)
    {
      if (current == l)
      {
        /* FOUND */
        while (My->StackHead != StackItem)
        {
          bwb_decexec ();
        }
        /* we are now the top item on the stack */
        return TRUE;
      }
      /* do NOT cross a function/sub boundary */
      switch (current->cmdnum)
      {
      case C_FUNCTION:
      case C_SUB:
      case C_GOSUB:
        /* NOT FOUND */
        return FALSE;
        /* break; */
      }
    }
  }
  /* NOT FOUND */
  return FALSE;
}

static LineType *
bwb_if_file (LineType * l, int ThenValue)
{
  /* IF END  # filenumber THEN linenumber */
  /* IF MORE # filenumber THEN linenumber */
  int Value;
  int FileNumber;
   
  assert (l != NULL);


  if (line_skip_FilenumChar (l))
  {
    /* IF END # */
    FileType *F;

    if (line_read_integer_expression (l, &FileNumber) == FALSE)
    {
      WARN_BAD_FILE_NUMBER;
      return (l);
    }
    if (FileNumber < 0)
    {
      /* Printer is NOT EOF */
      Value = FALSE;
    }
    else if (FileNumber == 0)
    {
      /* Console is NOT EOF */
      Value = FALSE;
    }
    else
    {
      /* normal file */
      F = find_file_by_number (FileNumber);
      if (F == NULL)
      {
        WARN_BAD_FILE_NUMBER;
        return (l);
      }
      /* if( TRUE ) */
      {
        /* actual file -- are we at the end? */
        FILE *fp;
        long current;
        long total;
        fp = F->cfp;
        assert( fp != NULL );
        current = ftell (fp);
        fseek (fp, 0, SEEK_END);
        total = ftell (fp);
        if (total == current)
        {
          /* EOF */
          Value = TRUE;
        }
        else
        {
          /* NOT EOF */
          Value = FALSE;
          fseek (fp, current, SEEK_SET);
        }
      }
    }
  }
  else
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }

  if (Value == ThenValue)
  {
    /* expression is TRUE, take THEN path */
    return bwb_then_else (l, TRUE);
  }
  /* expression is FALSE, take ELSE path */
  return bwb_then_else (l, FALSE);
}

static LineType *
bwb_then_else (LineType * l, int Value)
{
  /* 
     ... THEN 100 
     ... THEN 100 ELSE 200 

     The deciding expression has already been parsed and evaluated.
     If Value != 0, then we want to take the THEN path.
     If Value == 0, then we want to take the ELSE path.
   */
  int LineNumber;
  LineType *x;
   
  assert (l != NULL);

  if (line_skip_seperator (l))
  {
    /* OK */
  }
  else
  {
    /* OPTIONAL */
  }

  if (line_skip_word (l, "THEN"))
  {
    /* OK */
  }
  else if (line_skip_word (l, "GOTO"))
  {
    /* OK */
  }
  else
  {
    /* REQUIRED */
    WARN_SYNTAX_ERROR;
    return (l);
  }

  /* read THEN's LineNumber */
  if (line_read_integer_expression (l, &LineNumber) == FALSE)
  {
    WARN_SYNTAX_ERROR;
    return (l);
  }

  if (Value == 0)
  {
    /* expression is FALSE, take ELSE path */
    if (line_is_eol (l))
    {
      /* OPTIONAL */
      return (l);
    }

    if (line_skip_seperator (l))
    {
      /* OK */
    }
    else
    {
      /* OPTIONAL */
    }

    if (line_skip_word (l, "ELSE"))
    {
      /* OK */
    }
    else
    {
      /* REQUIRED */
      WARN_SYNTAX_ERROR;
      return (l);
    }

    if (line_read_integer_expression (l, &LineNumber) == FALSE)
    {
      WARN_SYNTAX_ERROR;
      return (l);
    }
  }

  x = NULL;
#if THE_PRICE_IS_RIGHT
  if (l->OtherLine != NULL)
  {
    /* look in the cache */
    if (l->OtherLine->number == LineNumber)
    {
      x = l->OtherLine;                /* found in cache */
    }
  }
#endif /* THE_PRICE_IS_RIGHT */
  if (x == NULL)
  {
    x = find_line_number (LineNumber);        /* bwb_then_else */
  }
  if (x != NULL)
  {
    line_skip_eol (l);
    x->position = 0;
#if THE_PRICE_IS_RIGHT
    l->OtherLine = x;                /* save in cache */
#endif /* THE_PRICE_IS_RIGHT */
    return x;
  }
  WARN_SYNTAX_ERROR;
  return (l);

}

static int
IsTypeMismatch (char LeftTypeCode, char RightTypeCode)
{
   
  if (LeftTypeCode == StringTypeCode && RightTypeCode == StringTypeCode)
  {
    /* both STRING */
    return FALSE;
  }
  if (LeftTypeCode != StringTypeCode && RightTypeCode != StringTypeCode)
  {
    /* both NUMBER */
    return FALSE;
  }
  /* TYPE MISMATCH */
  return TRUE;
}

static int
for_limit_check (DoubleType Value, DoubleType Target, DoubleType Step)
{
   
  if (Step > 0)
  {
    /* POSITIVE */
    if (Value > Target)
    {
      /* FOR I = 3 TO 2 STEP 1 */
      return TRUE;
    }
  }
  else
  {
    /* NEGATIVE */
    if (Value < Target)
    {
      /* FOR I = -3 TO -2 STEP -1 */
      return TRUE;
    }
  }
  return FALSE;
}

/* EOF */
