/***************************************************************
  
        bwb_cmd.c       Miscellaneous 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                            */
/*                                                               */
/*---------------------------------------------------------------*/


#include "bwbasic.h"



static int
xl_line(FILE * file, struct bwb_line * l);


static int
                LastLineNumber = -1;


/***************************************************************
  
   FUNCTION:       bwb_rem()
  
   DESCRIPTION:    This C function implements the BASIC rem
         (REMark) command, ignoring the remainder
         of the line.
  
***************************************************************/

struct bwb_line *
bwb_REM(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   adv_eos(l->buffer, &(l->position));
   return bwb_zline(l);
}

/***************************************************************
  
        FUNCTION:       bwb_let()
  
   DESCRIPTION:    This C function implements the BASIC
         LET assignment command, even if LET
         is implied and not explicit.
  
   SYNTAX:     LET variable = expression
  
***************************************************************/

struct bwb_line *
bwb_LET(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);


   /* Call the expression interpreter to evaluate the assignment */

   bwb_exp(l->buffer, TRUE, &(l->position));
   if (ERROR_PENDING)
   {
      return bwb_zline(l);
   }
   return bwb_zline(l);

}

/***************************************************************
  
        FUNCTION:       bwb_go
  
        DESCRIPTION: This C function implements the BASIC
         GO command, branching appropriately to
         GOTO or GOSUB.
  
***************************************************************/

struct bwb_line *
bwb_GO(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   bwb_error(err_syntax);

   return bwb_zline(l);

}

/***************************************************************
  
        FUNCTION:       bwb_goto
  
        DESCRIPTION: This C function implements the BASIC
         GOTO command.
  
      SYNTAX:     GOTO line | label
  
***************************************************************/


struct bwb_line *
bwb_GOTO(struct bwb_line * l)
{
   struct bwb_line *x;
   char            tbuf[BasicStringLengthMax + 1];

   bwx_DEBUG(__FUNCTION__);

   adv_ws(l->buffer, &(l->position));
   adv_element(l->buffer, &(l->position), tbuf);

   /* check for target label */
   x = find_label(tbuf);
   if (x != NULL)
   {
      x->position = 0;
      return x;
   }
   return bwb_zline(l);
}

/***************************************************************
  
        FUNCTION:       bwb_gosub()
  
   DESCRIPTION:    This function implements the BASIC GOSUB
         command.
  
      SYNTAX:     GOSUB line | label
  
***************************************************************/

struct bwb_line *
bwb_GOSUB(struct bwb_line * l)
{
   struct bwb_line *x;
   char            tbuf[BasicStringLengthMax + 1];

   bwx_DEBUG(__FUNCTION__);

   adv_ws(l->buffer, &(l->position));
   adv_element(l->buffer, &(l->position), tbuf);

   x = find_label(tbuf);
   if (x != NULL)
   {
      bwb_zline(l);

      bwb_incexec();
      /* set the new position to x and return x */
      x->position = 0;
      bwb_setexec(x, 0, EXEC_GOSUB);

      return x;
   }
   return bwb_zline(l);

}

/***************************************************************
  
        FUNCTION:       bwb_return()
  
   DESCRIPTION:    This function implements the BASIC RETURN
         command.
  
      SYNTAX:     RETURN
  
***************************************************************/

struct bwb_line *
bwb_RETURN(struct bwb_line * l)
{

   bwx_DEBUG(__FUNCTION__);

   while (CURTASK excs[CURTASK exsc].code != EXEC_GOSUB)
   {
      bwb_decexec();
      if (CURTASK excs[CURTASK exsc].code == EXEC_NORM)  /* End of the line? */
      {
         bwb_error(err_retnogosub);
      }
   }

   /* decrement the EXEC stack counter */

   bwb_decexec();

   return CURTASK excs[CURTASK exsc].line;

}

/***************************************************************
  
        FUNCTION:       bwb_on
  
        DESCRIPTION:    This function implements the BASIC ON...
                        GOTO or ON...GOSUB statements.
  
         It will also detect the ON ERROR... statement
         and pass execution to bwb_onerror().
  
      SYNTAX:     ON variable GOTO|GOSUB line|label[,...]
  
  
***************************************************************/

struct bwb_line *
bwb_ON(struct bwb_line * l)
{
   struct bwb_line *x;
   char            tbuf[BasicStringLengthMax + 1];
   int             p;
   struct exp_ese *rvar;
   int             v;
   int             loop;
   int             num_lines;
   int             command;

   bwx_DEBUG(__FUNCTION__);

   /* Check for argument */

   adv_ws(l->buffer, &(l->position));

   if (l->buffer[l->position] == '\0' || l->buffer[l->position] == OptionCommentChar)
   {
      bwb_error(err_incomplete);
      return bwb_zline(l);
   }
   /* get the variable name, numerical constant, or expression */

   adv_element(l->buffer, &(l->position), tbuf);

   /* evaluate the variable name or constant */

   p = 0;
   rvar = bwb_exp(tbuf, FALSE, &p);
   if (ERROR_PENDING)
   {
      return bwb_zline(l);
   }
   v = exp_getival(rvar);



   /* Get GOTO or GOSUB statements */

   adv_element(l->buffer, &(l->position), tbuf);
   if (strcasecmp(tbuf, "GO") == 0)
   {
      adv_ws(l->buffer, &(l->position));
      adv_element(l->buffer, &(l->position), tbuf);

      if (strcasecmp(tbuf, "TO") == 0)
      {
         command = C_GOTO;
      }
      else
      if (strcasecmp(tbuf, "SUB") == 0)
      {
         command = C_GOSUB;
      }
      else
      {
         sprintf(bwb_ebuf, ERR_ONNOGOTO);
         bwb_error(bwb_ebuf);

         return bwb_zline(l);
      }
   }
   else
   if (strcasecmp(tbuf, "GOTO") == 0)
   {
      command = C_GOTO;
   }
   else
   if (strcasecmp(tbuf, "GOSUB") == 0)
   {
      command = C_GOSUB;
   }
   else
   {
      sprintf(bwb_ebuf, ERR_ONNOGOTO);
      bwb_error(bwb_ebuf);

      return bwb_zline(l);
   }

   if (v < 1)
   {
      if (OptionFlags & OPTION_BUGS_ON)
      {
         /* fall-thru to next line without error */
         /* advance to end of segment */
         adv_eos(l->buffer, &(l->position));
      }
      else
      {
         /* ERROR */
         bwb_error(err_valoorange);
      }
      return bwb_zline(l);
   }
   num_lines = 0;

   loop = TRUE;
   while (loop == TRUE)
   {

      /* read a line|label */

      inp_adv(l->buffer, &(l->position));
      adv_element(l->buffer, &(l->position), tbuf);


      num_lines++;

      if (num_lines == v)
      {
         loop = FALSE;
      }
      /* check for end of line */

      adv_ws(l->buffer, &(l->position));
      if (l->buffer[l->position] == '\0' || l->buffer[l->position] == OptionCommentChar)
      {
         loop = FALSE;
      }
   }

   /* advance to end of segment */

   adv_eos(l->buffer, &(l->position));

   /* Be sure value is in range */

   if (v > num_lines)
   {
      if (OptionFlags & OPTION_BUGS_ON)
      {
         /* fall-thru to next line without error */
         /* advance to end of segment */
         adv_eos(l->buffer, &(l->position));
      }
      else
      {
         /* ERROR */
         bwb_error(err_valoorange);
      }
      return bwb_zline(l);
   }
   x = find_label(tbuf);
   if (x == NULL)
   {
      return bwb_zline(l);
   }
   if (command == C_GOTO)
   {


      x->position = 0;
      bwb_setexec(x, 0, CURTASK excs[CURTASK exsc].code);

      return x;

   }
   else
   if (command == C_GOSUB)
   {


      /* save current stack level */

      bwb_setexec(l, l->position, CURTASK excs[CURTASK exsc].code);

      /* increment exec stack */

      bwb_incexec();


      /* set the new position to x and return x */
      x->position = 0;
      bwb_setexec(x, 0, EXEC_GOSUB);

      return x;
   }
   sprintf(bwb_ebuf, "in bwb_on(): invalid value for command.");
   bwb_error(bwb_ebuf);

   return bwb_zline(l);

}

/***************************************************************
  
        FUNCTION:       bwb_stop()
  
   DESCRIPTION:    This C function implements the BASIC
         STOP command, interrupting program flow
         at a specific point.
  
      SYNTAX:     STOP
  
***************************************************************/

struct bwb_line *
bwb_STOP(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   stopped_line = l->number;
   bwx_STOP();

   return bwb_END(l);
}

/***************************************************************
  
        FUNCTION:       bwb_xend()
  
   DESCRIPTION:    This C function implements the BASIC
         END command, checking for END SUB
         or END FUNCTION, else stopping program
         execution for a simple END command.
  
***************************************************************/

struct bwb_line *
bwb_END(struct bwb_line * l)
{


   /* a simple END statement */
   bwx_DEBUG(__FUNCTION__);

   stopped_line = l->number;

   break_handler();

   return &CURTASK bwb_end;



}

/***************************************************************
  
        FUNCTION:       bwb_run()
  
        DESCRIPTION: This C function implements the BASIC
         RUN command.
  
         Even though RUN is not a core statement,
         the function bwb_run() is called from
         core, so it must be present for a minimal
         implementation.
  
      SYNTAX:     RUN [line]|[file-name]
  
***************************************************************/

struct bwb_line *
bwb_RUN(struct bwb_line * l)
{
   struct bwb_line *current;

   bwx_DEBUG(__FUNCTION__);

   /* see if there is an element */
   current = NULL;

   adv_ws(l->buffer, &(l->position));
   if (l->buffer[l->position] == '\0' || l->buffer[l->position] == OptionCommentChar)
   {
      current = CURTASK bwb_start.next;
      var_CLEAR();
   }
   else
   {
      struct exp_ese *e;

      e = bwb_exp(l->buffer, FALSE, &(l->position));
      if (ERROR_PENDING)
      {
         return bwb_zline(l);
      }
      if (e->type == STRING)
      {
         /* check its type: if it is a STRING, open the file
          * and execute it */
         FILE           *input;
         char            tbuf[BasicStringLengthMax + 1];

         bwb_NEW(l); /* clear memory */
         str_btoc(tbuf, exp_getsval(e));  /* get string in tbuf */
         if ((input = fopen(tbuf, "r")) == NULL)   /* open file */
         {
            sprintf(bwb_ebuf, err_openfile, tbuf);
            bwb_error(bwb_ebuf);
         }
         bwb_fload(input); /* load program */
         current = &CURTASK bwb_start; /* JBV */
      }
      else
      {
         /* check its type: if it is a NUMBER, execute the
          * line */
         int             go_lnumber;   /* line number to go to */
         struct bwb_line *x;

         go_lnumber = exp_getival(e);
         for (x = CURTASK bwb_start.next; x != &CURTASK bwb_end; x = x->next)
         {
            if (x->number == go_lnumber)
            {
               current = x;
            }
         }
         if (current == NULL)
         {
            sprintf(bwb_ebuf, err_lnnotfound, go_lnumber);
            bwb_error(bwb_ebuf);
            return &CURTASK bwb_end;
         }
      }
   }
   bwb_scan();

   current->position = 0;
   CURTASK         exsc = 0;
   bwb_setexec(current, 0, EXEC_NORM);


   /* RUN */
   bwb_Warning_Clear();
   stopped_line = 0;
   SetOnError(0);


   return current;

}

/***************************************************************
  
        FUNCTION:       bwb_cont()
  
        DESCRIPTION: This C function implements the BASIC
         CONT command.
  
      SYNTAX:     CONT
  
***************************************************************/

struct bwb_line *
bwb_CONT(struct bwb_line * l)
{
   struct bwb_line *current, *x;

   bwx_DEBUG(__FUNCTION__);


   /* see if there is an element */

   current = NULL;

   if (stopped_line > 0)
   {
      for (x = CURTASK bwb_start.next; (x != &CURTASK bwb_end) && (current == NULL); x = x->next)
      {
         if (x->number > stopped_line)
         {
            current = x;
         }
      }
   }
   if (current == NULL)
   {
      /* same as "RUN" */
      current = CURTASK bwb_start.next;
   }
   bwb_scan();

   current->position = 0;
   CURTASK         exsc = 0;
   bwb_setexec(current, 0, EXEC_NORM);

   /* CONT */
   stopped_line = 0;


   return current;

}


/***************************************************************
  
        FUNCTION:       bwb_new()
  
        DESCRIPTION: This C function implements the BASIC
         NEW command.
  
         Even though NEW is not a core statement,
         the function bwb_run() is called from
         core, so it must be present for a minimal
         implementation.
  
   SYNTAX:     NEW
  
***************************************************************/

struct bwb_line *
bwb_NEW(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   /* clear program in memory */
   bwb_xnew(&bwb_start);

   /* clear all variables */
   var_CLEAR();

   /* NEW */
   bwb_Warning_Clear();
   stopped_line = 0;
   SetOnError(0);


   return bwb_zline(l);

}

/* End of Core Functions Section */


/***************************************************************
  
        FUNCTION:       bwb_system()
  
   DESCRIPTION:    This C function implements the BASIC
         SYSTEM command, exiting to the operating
         system (or calling program).  It is also
         called by the QUIT command, a functional
         equivalent for SYSTEM in Bywater BASIC.
  
   SYNTAX:     SYSTEM
         QUIT
  
***************************************************************/
struct bwb_line *
bwb_QUIT(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);
   return bwb_SYSTEM(l);
}

struct bwb_line *
bwb_SYSTEM(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   prn_xprintf("\n");
   bwx_terminate();
   return &CURTASK bwb_end;/* to make LINT happy */

}

/***************************************************************
  
        FUNCTION:       bwb_load()
  
        DESCRIPTION: This C function implements the BASIC
         LOAD command.
  
      SYNTAX:     LOAD [file-name]
  
***************************************************************/

struct bwb_line *
bwb_LOAD(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   /* clear current contents */
   bwb_NEW(l);

   /* call xload function to load program in memory */

   bwb_xload(l);

   return bwb_zline(l);

}

/***************************************************************
  
        FUNCTION:       bwb_xload()
  
        DESCRIPTION: This C function loads a BASIC program
         into memory.
  
***************************************************************/

struct bwb_line *
bwb_xload(struct bwb_line * l)
{
   FILE           *loadfile;

   bwx_DEBUG(__FUNCTION__);

   /* Get an argument for filename */

   adv_ws(l->buffer, &(l->position));

   if (l->buffer[l->position] == '\0' || l->buffer[l->position] == OptionCommentChar)
   {
      /* default is the last filename used by LOAD or SAVE */
   }
   else
   {
      /* Section added by JBV (bug found by DD) */
      struct exp_ese *e;   /* JBV */
      e = bwb_exp(l->buffer, FALSE, &(l->position));
      if (ERROR_PENDING)
      {
         return bwb_zline(l);
      }
      if (e->type != STRING)
      {
         sprintf(bwb_ebuf, "in bwb_xload(): Missing filespec");
         bwb_error(bwb_ebuf);
         return bwb_zline(l);
      }
      str_btoc(CURTASK progfile, exp_getsval(e));  /* JBV */
   }
   if (strlen(CURTASK progfile) == 0)
   {
      bwb_error(err_nofn); /* Added by JBV (bug found by DD) */
      return bwb_zline(l);
   }
   if ((loadfile = fopen(CURTASK progfile, "r")) == NULL)
   {
      sprintf(bwb_ebuf, err_openfile, CURTASK progfile);
      bwb_error(bwb_ebuf);

      return bwb_zline(l);
   }
   bwb_fload(loadfile);


   return bwb_zline(l);
}

/***************************************************************
  
        FUNCTION:       bwb_save()
  
   DESCRIPTION:    This C function implements the BASIC
         SAVE command.
  
      SYNTAX:     SAVE [file-name]
  
***************************************************************/
#define CSAVE_VERSION_1 0x20150218

struct bwb_line *
bwb_CSAVE_(struct bwb_line * l)
{
   /* CSAVE* NumericArrayName */
   char            tbuf[BasicStringLengthMax + 1];
   struct bwb_variable *v;
   FILE           *f;
   unsigned long   n;
   unsigned long   t;


   bwx_DEBUG(__FUNCTION__);

   bwb_getvarname(l->buffer, tbuf, &(l->position));
   v = var_find(tbuf);
   if (v == NULL)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* variable MUST be numeric */
   if (v->type == STRING)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* variable MUST be an array */
   if ((v->dimensions == 1) && (v->array_sizes[0] == 1))
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* * variable storage is a mess, * we bypass that tradition here. */
   t = 1;
   for (n = 0; n < v->dimensions; n++)
   {
      t *= v->array_sizes[n];
   }
   if (t <= 1)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* open file */
   f = fopen(v->name, "w");
   if (f == NULL)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* write version number */
   n = CSAVE_VERSION_1;
   fwrite(&n, sizeof(long), 1, f);
   /* write total number of elements */
   fwrite(&t, sizeof(long), 1, f);
   /* write data */
   fwrite(v->memnum, sizeof(BasicNumberType), t, f);
   /* OK */
   fclose(f);
   return bwb_zline(l);
}


struct bwb_line *
bwb_CLOAD_(struct bwb_line * l)
{
   /* CLOAD* NumericArrayName */
   char            tbuf[BasicStringLengthMax + 1];
   struct bwb_variable *v;
   FILE           *f;
   unsigned long   n;
   unsigned long   t;


   bwx_DEBUG(__FUNCTION__);

   bwb_getvarname(l->buffer, tbuf, &(l->position));
   v = var_find(tbuf);
   if (v == NULL)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* variable MUST be numeric */
   if (v->type == STRING)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* variable MUST be an array */
   if ((v->dimensions == 1) && (v->array_sizes[0] == 1))
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* variable storage is a mess, we bypass that tradition here. */
   t = 1;
   for (n = 0; n < v->dimensions; n++)
   {
      t *= v->array_sizes[n];
   }
   if (t <= 1)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* open file */
   f = fopen(v->name, "r");
   if (f == NULL)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* read version number */
   n = 0;
   fread(&n, sizeof(long), 1, f);
   if (n != CSAVE_VERSION_1)
   {
      fclose(f);
      bwb_error("Bad File");
      return bwb_zline(l);
   }
   /* read total number of elements */
   n = 0;
   fread(&n, sizeof(long), 1, f);
   if (n != t)
   {
      fclose(f);
      bwb_error("Size Mismatch");
      return bwb_zline(l);
   }
   /* read data */
   fread(v->memnum, sizeof(BasicNumberType), t, f);
   /* OK */
   fclose(f);
   return bwb_zline(l);
}



struct bwb_line *
bwb_CSAVE(struct bwb_line * l)
{
   /* CSAVE [filename$] */
   bwx_DEBUG(__FUNCTION__);
   return bwb_SAVE(l);
}

struct bwb_line *
bwb_CLOAD(struct bwb_line * l)
{
   /* CLOAD [filename$] */
   bwx_DEBUG(__FUNCTION__);
   return bwb_LOAD(l);
}


struct bwb_line *
bwb_SAVE(struct bwb_line * l)
{
   FILE           *outfile;

   bwx_DEBUG(__FUNCTION__);


   /* Get an argument for filename */

   adv_ws(l->buffer, &(l->position));
   if (l->buffer[l->position] == '\0' || l->buffer[l->position] == OptionCommentChar)
   {
      /* default is the last filename used by LOAD or SAVE */
   }
   else
   {
      /* Section added by JBV (bug found by DD) */
      struct exp_ese *e;   /* JBV */
      e = bwb_exp(l->buffer, FALSE, &(l->position));
      if (ERROR_PENDING)
      {
         return bwb_zline(l);
      }
      if (e->type != STRING)
      {
         sprintf(bwb_ebuf, "in bwb_save(): Missing filespec");
         bwb_error(bwb_ebuf);
         return bwb_zline(l);
      }
      str_btoc(CURTASK progfile, exp_getsval(e));  /* JBV */
   }
   if (strlen(CURTASK progfile) == 0)
   {
      bwb_error(err_nofn); /* Added by JBV (bug found by DD) */
      return bwb_zline(l);
   }
   if ((outfile = fopen(CURTASK progfile, "w")) == NULL)
   {
      sprintf(bwb_ebuf, err_openfile, CURTASK progfile);
      bwb_error(bwb_ebuf);

      return bwb_zline(l);
   }
   bwb_xlist(l, outfile);
   fclose(outfile);


   return bwb_zline(l);

}

/***************************************************************
  
        FUNCTION:       bwb_list()
  
        DESCRIPTION: This C function implements the BASIC
         LIST command.
  
      SYNTAX:     LIST line[-line]
  
***************************************************************/

struct bwb_line *
bwb_LIST(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   bwb_xlist(l, stdout);
   return bwb_zline(l);
}

/***************************************************************
  
        FUNCTION:       bwb_xlist()
  
        DESCRIPTION: This C function lists the program in
         memory to a specified output device.
  
***************************************************************/

struct bwb_line *
bwb_xlist(struct bwb_line * l, FILE * file)
{
   struct bwb_line *start, *end, *current;
   BasicLineNumberType s, e;
   int             f, r;

   bwx_DEBUG(__FUNCTION__);

   /* Default first value */
   start = CURTASK bwb_start.next;
   s = BasicLineNumberMin;


   /* Default last value */
   end = &CURTASK bwb_end;
   e = BasicLineNumberMax;


   /* Parse parameters */
   r = bwb_numseq(&(l->buffer[l->position]), &s, &e);
/*  
r example
= ================
0 LIST
1 LIST 1000
4 LIST     -
5 LIST 1000-
6 LIST     -9000
7 LIST 1000-9000
*/



   if (r == 1)
   {
      /* LIST 1000 */
      e = s;
   }
   /* advance to the end of the segment */
   adv_eos(l->buffer, &(l->position));


   /* abort if either number is out of range */
   if (s < BasicLineNumberMin || s > BasicLineNumberMax)
   {
      /* LIST 99999- */
      sprintf(bwb_ebuf, err_lnnotfound, s);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   if (e < BasicLineNumberMin || e > BasicLineNumberMax)
   {
      /* LIST -99999 */
      sprintf(bwb_ebuf, err_lnnotfound, e);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   if (s > e)
   {
      /* LIST 9999-1 */
      sprintf(bwb_ebuf, err_lnnotfound, s);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   /* find the first line that actually exists that is >= START */
   f = FALSE;
   for (current = CURTASK bwb_start.next; (f == FALSE) && (current != &CURTASK bwb_end); current = current->next)
   {
      if (current->number >= s)
      {
         f = TRUE;
         start = current;
      }
   }
   /* "start" now points at the first line that actually exists that is
    * >= START */

   if (r == 1)
   {
      /* LIST 999 */
      if (s != start->number)
      {
         /* THE SPECIFIED LINE DOES NOT EXIST */
         f = FALSE;
      }
   }
   /* check and see if a line number was found */
   if (f == FALSE)
   {
      if (l->number > 0)
      {
         /* this is an executing program */
         sprintf(bwb_ebuf, err_lnnotfound, s);
         bwb_error(bwb_ebuf);
      }
      return bwb_zline(l);
   }
   f = FALSE;
   if (e > s)
   {
      /* find the last line that actually exists that is <= END */
      for (current = start; current != &CURTASK bwb_end; current = current->next)
      {
         if (current->number <= e)
         {
            f = TRUE;
            end = current;
         }
      }
   }
   else
   {
      /* e == s */
      f = TRUE;
      end = start;
   }
   /* "end" now points at the last line that actually exists that is <=
    * END */



   if (r == 6 || r == 7)
   {
      /* LIST -999 */
      if (e < end->number)
      {
         /* THERE ARE NO LINES LESS THAN SPECIFIED */
         f = FALSE;
      }
   }
   /* "end" is always after "start" */
   end = end->next;
   while (end->number <= e)
   {
      end = end->next;
   }
   /* * "end" now points at the first line * that actually exists that
    * is > END */


   /* check and see if a line number was found */
   if (f == FALSE)
   {
      if (l->number > 0)
      {
         /* this is an executing program */
         sprintf(bwb_ebuf, err_lnnotfound, e);
         bwb_error(bwb_ebuf);
      }
      return bwb_zline(l);
   }
   /* now go through and list appropriate lines */
   bwb_scan();
   LastLineNumber = -1;
   for (current = start; current != end; current = current->next)
   {
      xl_line(file, current);
   }
   fprintf(file, "\n");

   return bwb_zline(l);


}

/***************************************************************
  
   FUNCTION:       xl_line()
  
   DESCRIPTION:    This function lists a single program
         line to a specified device of file.
         It is called by bwb_xlist();
  
***************************************************************/

static int
xl_line(FILE * file, struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   if ((file == stdout) || (file == stderr))
   {
      char            tbuf[32];


      if (LastLineNumber == l->number)
      {
         strcpy(tbuf, "     :");
      }
      else
      {
         sprintf(tbuf, "%5d:", l->number);
      }
      prn_xprintf(tbuf);

      if (OptionFlags & OPTION_COVERAGE_ON)
      {
#if 0
         sprintf(tbuf, "%c:", l->Coverage);
         prn_xprintf(tbuf);
#endif
         if( l->LineFlags & LINE_EXECUTED )
         {
             prn_xprintf("*");
         }
         else
         {
             prn_xprintf(" ");
         }
         prn_xprintf(":");
      }
      if (OptionIndentValue > 0)
      {
         int             i;
         for (i = 0; i < l->Indention; i++)
         {
            int             j;
            for (j = 0; j < OptionIndentValue; j++)
            {
               prn_xprintf(" ");
            }
         }
      }
      prn_xprintf(l->buffer);
      prn_xprintf("\n");

   }
   else
   {

#if 0
      if (LastLineNumber == l->number)
      {
         fprintf(file, "%s\n", l->buffer);
      }
      else
      {
         fprintf(file, "%d %s\n", l->number, l->buffer);
      }
#endif
      if (LastLineNumber == l->number && OptionStatementChar != '\0' )
      {
          fprintf(file, "%c", OptionStatementChar);
      }
      else
      if(LastLineNumber == -1 )
      {
          /* first line, do nothing */
      }
      else
      {
          fprintf(file, "\n");
      }        
      
      if( l->LineFlags & LINE_NUMBERED )
      {
         fprintf(file, "%d ", l->number);
      }
      
      fprintf(file, "%s", l->buffer);
   }
   LastLineNumber = l->number;

   return TRUE;
}

/***************************************************************
  
        FUNCTION:       bwb_delete()
  
   DESCRIPTION:    This C function implements the BASIC
         DELETE command for interactive programming,
         deleting a specified program line (or lines)
         from memory.
  
      SYNTAX:     DELETE line[-line]
  
***************************************************************/


struct bwb_line *
bwb_DELETE(struct bwb_line * l)
{
   struct bwb_line *start, *end, *current, *previous, *p, *next;
   BasicLineNumberType s, e;
   int             f, r;

   bwx_DEBUG(__FUNCTION__);


   /* Default first value */
   start = CURTASK bwb_start.next;
   s = BasicLineNumberMin;


   /* Default last value */
   end = &CURTASK bwb_end;
   e = BasicLineNumberMax;


   /* Default previous value */
   previous = p = &CURTASK bwb_start;


   r = bwb_numseq(&(l->buffer[l->position]), &s, &e);
/*  
r example
= ================
0 DELETE
1 DELETE 1000
4 DELETE     -
5 DELETE 1000-
6 DELETE     -9000
7 DELETE 1000-9000
*/

   if (r == 1)
   {
      /* DELETE 1000 */
      e = s;
   }
   /* advance to the end of the segment */
   adv_eos(l->buffer, &(l->position));


   /* abort if either number is out of range */
   if (s < BasicLineNumberMin || s > BasicLineNumberMax)
   {
      /* DELETE 99999- */
      sprintf(bwb_ebuf, err_lnnotfound, s);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   if (e < BasicLineNumberMin || e > BasicLineNumberMax)
   {
      /* DELETE -99999 */
      sprintf(bwb_ebuf, err_lnnotfound, e);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   if (s > e)
   {
      /* DELETE 9999-1 */
      sprintf(bwb_ebuf, err_lnnotfound, s);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   /* find the first line that exists that is >= START */
   f = FALSE;
   for (current = CURTASK bwb_start.next; (f == FALSE) && (current != &CURTASK bwb_end); current = current->next)
   {
      if (current->number >= s)
      {
         f = TRUE;
         previous = p;
         start = current;
      }
      p = current;
   }
   /* "start" now points at the first line that actually exists that is
    * >= START */
   /* "previous" now points at the first line that actually exists that
    * is < START */


   if (r == 1)
   {
      /* DELETE 1000 */
      if (s != start->number)
      {
         /* THE SPECIFIED LINE DOES NOT EXIST */
         f = FALSE;
      }
   }
   /* check and see if a line number was found */
   if (f == FALSE)
   {
      if (l->number > 0)
      {
         /* this is an executing program */
         sprintf(bwb_ebuf, err_lnnotfound, s);
         bwb_error(bwb_ebuf);
      }
      return bwb_zline(l);
   }
   f = FALSE;
   if (e > s)
   {
      /* find the last line that actually exists that is <= END */
      for (current = start; current != &CURTASK bwb_end; current = current->next)
      {
         if (current->number <= e)
         {
            f = TRUE;
            end = current;
         }
      }
   }
   else
   {
      /* e == s */
      f = TRUE;
      end = start;
   }
   /* "end" now points at the last line that actually exists that is <=
    * END */


   if (r == 6 || r == 7)
   {
      /* DELETE -999 */
      /* DELETE 1-999 */
      if (e < end->number)
      {
         /* THERE ARE NO LINES LESS THAN SPECIFIED */
         f = FALSE;
      }
   }
   /* "end" is always after "start" */
   end = end->next;
   while (end->number <= e)
   {
      end = end->next;
   }
   /* * "end" now points at the first line * that actually exists * that
    * is > END */


   /* check and see if a line number was found */
   if (f == FALSE)
   {
      if (l->number > 0)
      {
         /* this is an executing program */
         sprintf(bwb_ebuf, err_lnnotfound, e);
         bwb_error(bwb_ebuf);
      }
      return bwb_zline(l);
   }
   if (l->number > 0)
   {
      /* This is an executing progrram. So make sure DELETE removes
       * structured commands completely. The BASIC program must
       * delete all of a structured command. Do not allow a
       * dangling FOR, NEXT, and so on, since that is just a FATAL
       * error waiting to happen. FOR - NEXT DO - LOOP WHILE - WEND
       * UNTIL - UEND IF - END IF SELECT CASE - END SELECT */
      s = start->number;
      e = end->number;
      current = start;
      while ((current != end) && (current != &CURTASK bwb_end))
      {
         if (current->OtherLine != NULL)
         {
            /* this line is either the Top or the Bottomm
             * of a structured command */
            /* verify the other end of the block is also
             * in the DELETE range */
            int             n;
            n = current->OtherLine->number;
            if (n < s || n >= e)
            {
               sprintf(bwb_ebuf,
                  "Cannot delete at %d because %d is not included"
                  ,current->number
                  ,n
                  );
               bwb_error(bwb_ebuf);
               return bwb_zline(l);
            }
         }
         current = current->next;
      }
   }
   else
   {
      /* The user is executing DELETE interactively,  Allow user to
       * DELETE whatever they want. Whenever user attempts to run,
       * bwb_scan() will find any mismatch. */
   }


   /* now go through and delete appropriate lines */
   current = start;
   while ((current != end) && (current != &CURTASK bwb_end))
   {
      next = current->next;
      if (current == l)
      {
         l = previous;
      }
      if (current->OtherLine != NULL)
      {
         /* remove any existing structured command linkage */
         current->OtherLine->OtherLine = NULL;
         current->OtherLine = NULL;
      }
      bwb_freeline(current);
      current = next;
   }


   /* previous should now be set to the line previous to the first in
    * the omission list */
   previous->next = current;


   CURTASK         rescan = TRUE;   /* program needs to be scanned again */
   bwb_scan();

   return bwb_zline(l);


}


/***************************************************************
  
        FUNCTION:       bwb_donum()
  
   DESCRIPTION:    This function implements the BASIC DO
         NUM command, numbering all program lines
         in memory in increments of 10 beginning
         at 10.
  
   SYNTAX:     DO NUM
  
***************************************************************/
#if 0
struct bwb_line *
bwb_donum(struct bwb_line * l)
{
   struct bwb_line *current;
   register int    lnumber;

   bwx_DEBUG(__FUNCTION__);

   lnumber = 10;
   for (current = bwb_start.next; current != &bwb_end; current = current->next)
   {
      current->number = lnumber;

      lnumber += 10;
      if (lnumber > BasicLineNumberMax)
      {
         return bwb_zline(l);
      }
   }

   return bwb_zline(l);
}

/***************************************************************
  
        FUNCTION:       bwb_dounnum()
  
   DESCRIPTION:    This function implements the BASIC DO
         UNNUM command, removing all line numbers
         from the program in memory.
  
   SYNTAX:     DO UNNUM
  
***************************************************************/

struct bwb_line *
bwb_dounnum(struct bwb_line * l)
{
   struct bwb_line *current;

   bwx_DEBUG(__FUNCTION__);

   for (current = bwb_start.next; current != &bwb_end; current = current->next)
   {
      current->number = 0;
   }

   return bwb_zline(l);
}

#endif            /* 0 */


/***************************************************************
  
        FUNCTION:       bwb_chain()
  
        DESCRIPTION: This C function implements the BASIC
         CHAIN command.
  
      SYNTAX:     CHAIN "file-name" | file-name$
  
***************************************************************/

struct bwb_line *
bwb_CHAIN(struct bwb_line * l)
{
   /* based upon bwb_xload() */
   FILE           *loadfile;
   struct exp_ese *e;

   bwx_DEBUG(__FUNCTION__);

   /* Get an argument for filename */

   adv_ws(l->buffer, &(l->position));
   if (l->buffer[l->position] == '\0' || l->buffer[l->position] == OptionCommentChar)
   {
      bwb_error(err_nofn); /* Added by JBV (bug found by DD) */
      return bwb_zline(l);
   }
   /* The file-name may be any string expression */
   e = bwb_exp(l->buffer, FALSE, &(l->position));
   if (ERROR_PENDING)
   {
      return bwb_zline(l);
   }
   if (e->type != STRING)
   {
      sprintf(bwb_ebuf, "in bwb_chain(): Missing filespec");
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   /* save the filename */
   str_btoc(CURTASK progfile, exp_getsval(e));

   /* deallocate all variables except common ones */
   var_delcvars();

   /* remove old program from memory */
   bwb_xnew(&bwb_start);


   /* call xload function to load new program in memory */

   if ((loadfile = fopen(CURTASK progfile, "r")) == NULL)
   {
      sprintf(bwb_ebuf, err_openfile, CURTASK progfile);
      bwb_error(bwb_ebuf);

      return bwb_zline(l);
   }
   bwb_fload(loadfile);

   bwb_scan();

   /* reset all stack counters */
   CURTASK         exsc = 0;
   CURTASK         expsc = 0;
   /* run the program */

   /* CHAIN */
   bwb_Warning_Clear();
   stopped_line = 0;
   SetOnError(0);

   CURTASK         bwb_start.position = 0;
   bwb_setexec(&CURTASK bwb_start, 0, EXEC_NORM);
   return &CURTASK bwb_start;
}


/***************************************************************
  
        FUNCTION:       bwb_merge()
  
        DESCRIPTION: This C function implements the BASIC
         MERGE command, merging command lines from
         a specified file into the program in memory
         without deleting the lines already in memory.
  
      SYNTAX:     MERGE file-name
  
***************************************************************/

struct bwb_line *
bwb_MERGE(struct bwb_line * l)
{

   bwx_DEBUG(__FUNCTION__);

   /* call xload function to merge program in memory */

   bwb_xload(l);


   return bwb_zline(l);

}



void
SetOnError(int LineNumber)
{
   /* scan the stack looking for a FUNCTION/SUB */
   int             i;

   bwx_DEBUG(__FUNCTION__);

   i = CURTASK exsc;
   while (i > 0)
   {
      struct bwb_line *current;

      current = CURTASK excs[i].LoopTopLine;
      if (current != NULL)
      {
         switch (current->cmdnum)
         {
         case C_FUNCTION:
         case C_SUB:
            /* FOUND */
            /* we are in a FUNCTION/SUB, so this is LOCAL */
            CURTASK excs[i].OnErrorGoto = LineNumber;
            return;
            break;
         }
      }
      i--;
   }
   /* NOT FOUND */
   /* we are NOT in a FUNCTION/SUB  */
   CURTASK         excs[0].OnErrorGoto = LineNumber;
}


int
GetOnError(void)
{
   /* scan the stack looking for a FUNCTION/SUB */
   int             i;

   bwx_DEBUG(__FUNCTION__);



   i = CURTASK exsc;
   while (i >= 0)
   {
      if (CURTASK excs[i].OnErrorGoto != 0)
      {
         /* FOUND */
         return CURTASK excs[i].OnErrorGoto;
      }
      i--;
   }
   /* NOT FOUND */
   return 0;
}


/***************************************************************
  
        FUNCTION:       bwb_onerror()
  
        DESCRIPTION: This C function implements the BASIC
            ON ERROR GOSUB command.
  
      SYNTAX:     ON ERROR GOSUB line | label
  
***************************************************************/

struct bwb_line *
bwb_ON_ERROR_GOTO(struct bwb_line * l)
{
  /* ON ERROR GOTO line */
  struct bwb_line *x;
  char            tbuf[BasicStringLengthMax + 1];
  /* get the line number */
  adv_ws(l->buffer, &(l->position));
  adv_element(l->buffer, &(l->position), tbuf);
  if (strcasecmp(tbuf, "0") == 0)
  {
     SetOnError(0);
     return bwb_zline(l);
  }
  x = find_label(tbuf);
  if (x != NULL)
  {
     SetOnError(x->number);
     return bwb_zline(l);
  }
   bwb_error(err_syntax);

   return bwb_zline(l);
}

struct bwb_line *
bwb_ON_ERROR_GOSUB(struct bwb_line * l)
{
  /* ON ERROR GOSUB line */
  struct bwb_line *x;
  char            tbuf[BasicStringLengthMax + 1];
  /* get the line number */
  adv_ws(l->buffer, &(l->position));
  adv_element(l->buffer, &(l->position), tbuf);
  if (strcasecmp(tbuf, "0") == 0)
  {
     SetOnError(0);
     return bwb_zline(l);
  }
  x = find_label(tbuf);
  if (x != NULL)
  {
     SetOnError(x->number);
     return bwb_zline(l);
  }
   bwb_error(err_syntax);

   return bwb_zline(l);
}

struct bwb_line *
bwb_ON_ERROR_RESUME_NEXT(struct bwb_line * l)
{
     SetOnError(-1);
     return bwb_zline(l);
}

struct bwb_line *
bwb_ON_ERROR_RETURN_NEXT(struct bwb_line * l)
{
     SetOnError(-1);
     return bwb_zline(l);
}

struct bwb_line *
bwb_ON_TIMER(struct bwb_line * l)
{
   /* ON TIMER(...) GOSUB ... TIMER ON ... */
   char            tbuf[BasicStringLengthMax + 1];
   int             p;
   struct exp_ese *rvar;
   BasicNumberType v;
   BasicNumberType minv;


   bwx_DEBUG(__FUNCTION__);

   /* turn off the TIMER - so syntax errors will NOT fire the timer */
   bwb_Timer_Off();
   tmr_gotol = 0;
   tmr_count = 0;


   /* get the SECOMDS parameter */
   adv_element(l->buffer, &(l->position), tbuf);

   p = 0;
   rvar = bwb_exp(tbuf, FALSE, &p);
   if (ERROR_PENDING)
   {
      return bwb_zline(l);
   }
   v = exp_getnval(rvar);
   minv = 1;
   minv /= CLOCKS_PER_SEC;

   if (v > minv)
   {
      /* get the GOSUB keyword */
      adv_ws(l->buffer, &(l->position));
      adv_element(l->buffer, &(l->position), tbuf);

      if (strcasecmp(tbuf, "GOSUB") == 0)
      {
         /* ON TIMER(X) GOSUB line */
         struct bwb_line *x;
         /* get the line number */
         adv_ws(l->buffer, &(l->position));
         adv_element(l->buffer, &(l->position), tbuf);
         x = find_label(tbuf);
         if (x != NULL)
         {
            tmr_gotol = x->number;
            tmr_count = v;
            return bwb_zline(l);
         }
      }
   }
   bwb_error(err_syntax);

   return bwb_zline(l);

}


struct bwb_line *
bwb_TIMER(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   /* turn off the TIMER - so syntax errors will NOT fire the timer */
   bwb_Timer_Off();

   bwb_error(err_syntax);

   return bwb_zline(l);
}

struct bwb_line *
bwb_TIMER_OFF(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   /* turn off the TIMER - so syntax errors will NOT fire the timer */
   bwb_Timer_Off();

   /* TIMER OFF */
   tmr_gotol = 0;
   tmr_count = 0;

   return bwb_zline(l);
}

struct bwb_line *
bwb_TIMER_ON(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   /* turn off the TIMER - so syntax errors will NOT fire the timer */
   bwb_Timer_Off();

   /* TIMER ON */
   if (tmr_count > 0 && tmr_gotol > 0)
   {
      tmr_expires = bwx_TIMER(tmr_count);
      bwb_Timer_On();
   }

   return bwb_zline(l);
}

struct bwb_line *
bwb_TIMER_STOP(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   /* turn off the TIMER - so syntax errors will NOT fire the timer */
   bwb_Timer_Off();

   /* TIMER STOP */

   return bwb_zline(l);
}

/***************************************************************
  
        FUNCTION:       bwb_resume()
  
        DESCRIPTION: This C function implements the BASIC
            RESUME command.
  
      SYNTAX:     RESUME [ 0 | NEXT | line | label ]
  
***************************************************************/
struct bwb_line *
bwb_RESUME(struct bwb_line * l)
{
   char            tbuf[BasicStringLengthMax + 1];
   struct bwb_line *x = NULL;

   bwx_DEBUG(__FUNCTION__);

   if (err_number == 0) /* RESUME without ERR are FATAL */
   {
      sprintf(bwb_ebuf, "RESUME WITHOUT ERR AT %d", CURTASK number);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   if (err_line == NULL)
   {
      /* RESUME without ERL are FATAL */
      sprintf(bwb_ebuf, "RESUME WITHOUT ERL AT %d", CURTASK number);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   if (err_line->number == 0)
   {
      /* RESUME without ERL are FATAL */
      sprintf(bwb_ebuf, "RESUME WITHOUT ERL AT %d", CURTASK number);
      bwb_error(bwb_ebuf);
      return bwb_zline(l);
   }
   /* Get optional argument for RESUME */

   adv_ws(l->buffer, &(l->position));
   adv_element(l->buffer, &(l->position), tbuf);


   if (strlen(tbuf) == 0 || strcasecmp(tbuf, "0") == 0)
   {
      /* RESUME [0] */
      /* Execution resumes at the statement which caused the error */
      x = err_line;
   }
   else
   if (strcasecmp(tbuf, "NEXT") == 0)
   {
      /* RESUME NEXT */
      /* Execution resumes at the statement immediately following
       * the one which caused the error */
       x = err_line->next;
   }
   else
   {
      /* RESUME line|label */
      x = find_label(tbuf);
   }



   if (x != NULL)
   {
      bwb_Warning_Clear();
      x->position = 0;
      return x;
   }
   return bwb_zline(l);
}





/***************************************************************
  
        FUNCTION:       bwb_xnew()
  
        DESCRIPTION: Clears the program in memory, but does not
         deallocate all variables.
  
***************************************************************/

void
bwb_xnew(struct bwb_line * l)
{
   struct bwb_line *current, *previous;
   int             wait;


   bwx_DEBUG(__FUNCTION__);

   previous = NULL;  /* JBV */
   wait = TRUE;
   for (current = l->next; current != &CURTASK bwb_end; current = current->next)
   {
      if (wait != TRUE)
      {
         /* Revised to FREE pass-thru call by JBV */
         FREE(previous, "bwb_xnew");
      }
      wait = FALSE;
      previous = current;
   }

   l->next = &bwb_end;

}


/***************************************************************
  
        FUNCTION:       bwb_cmds()
  
   DESCRIPTION:    This function implements a CMD command,
         which lists all commands implemented.
         It is not part of a BASIC specification,
         but is used for debugging bwBASIC.
  
   SYNTAX:     CMDS
  
***************************************************************/


struct bwb_line *
bwb_CMDS(struct bwb_line * l)
{
   register int    n;
   int             t;

   bwx_DEBUG(__FUNCTION__);

   prn_iprintf("BWBASIC COMMANDS AVAILABLE:\n");

   /* run through the command table and print comand names */

   t = 0;
   for (n = 0; n < NUM_COMMANDS; ++n)
   {
      prn_iprintf(bwb_cmdtable[n].name);
      if (t < 4)
      {
         prn_iprintf("\t");
         t++;
      }
      else
      {
         prn_iprintf("\n");
         t = 0;
      }
   }
   if (t > 0)
   {
      prn_iprintf("\n");
   }
   return bwb_zline(l);
}

static void
CommandUniqueID(int i, char *UniqueID)
{
   char           *C;

   bwx_DEBUG(__FUNCTION__);

   strcpy(UniqueID, "C_");

   if (strcasecmp(bwb_cmdtable[i].name, "?") == 0)
   {
      strcat(UniqueID, "QUEST");
   }
   else
   {
      strcat(UniqueID, bwb_cmdtable[i].name);
   }
   C = UniqueID;
   while (*C != '\0')
   {
      if (!isalnum(*C))
         *C = '_';
      C++;
   }
}

static void
CommandVector(int i, char *Vector)
{
   char           *C;

   bwx_DEBUG(__FUNCTION__);

   strcpy(Vector, "bwb_");

   if (strcasecmp(bwb_cmdtable[i].name, "?") == 0)
   {
      strcat(Vector, "QUEST");
   }
   else
   {
      strcat(Vector, bwb_cmdtable[i].name);
   }
   C = Vector;
   while (*C != '\0')
   {
      if (!isalnum(*C))
         *C = '_';
      C++;
   }
}

static void
CommandOptionVersion(int n, char *OutputLine)
{
   int             i;
   int             j;

   bwx_DEBUG(__FUNCTION__);

   strcpy(OutputLine, "");
   j = 0;
   for (i = 0; i < NUM_VERSIONS; i++)
   {
      if (bwb_cmdtable[n].OptionVersionBitmask & bwb_vertable[i].OptionVersionBitmask)
      {
         if (j > 0)
         {
            strcat(OutputLine, " | ");
         }
         strcat(OutputLine, bwb_vertable[i].ID);
         j++;
      }
   }
}

void
SortAllCommands(void)
{
   static int      IsSorted = FALSE;

   bwx_DEBUG(__FUNCTION__);

   if (IsSorted == FALSE)
   {
      int             i;
      for (i = 0; i < NUM_COMMANDS - 1; i++)
      {
         int             j;
         int             k;
         k = i;
         for (j = i + 1; j < NUM_COMMANDS; j++)
         {
            if (strcasecmp(bwb_cmdtable[j].name, bwb_cmdtable[k].name) < 0)
            {
               k = j;
            }
         }
         if (k > i)
         {
            struct bwb_command t;
            memcpy(&t, &(bwb_cmdtable[i]), sizeof(struct bwb_command));
            memcpy(&(bwb_cmdtable[i]), &(bwb_cmdtable[k]), sizeof(struct bwb_command));
            memcpy(&(bwb_cmdtable[k]), &t, sizeof(struct bwb_command));
         }
      }
      IsSorted = TRUE;
   }
}

void
SortAllFunctions(void)
{
   static int      IsSorted = FALSE;

   bwx_DEBUG(__FUNCTION__);

   if (IsSorted == FALSE)
   {
      int             i;
      for (i = 0; i < NUM_FUNCTIONS - 1; i++)
      {
         int             j;
         int             k;
         k = i;
         for (j = i + 1; j < NUM_FUNCTIONS; j++)
         {
            int             n;
            n = strcasecmp(bwb_prefuncs[j].Name, bwb_prefuncs[k].Name);
            if (n < 0)
            {
               k = j;
            }
            else
            if (n == 0)
            {
               if (bwb_prefuncs[j].ParameterCount < bwb_prefuncs[k].ParameterCount)
               {
                  k = j;
               }
            }
         }
         if (k > i)
         {
            struct bwb_function t;
            memcpy(&t, &(bwb_prefuncs[i]), sizeof(struct bwb_function));
            memcpy(&(bwb_prefuncs[i]), &(bwb_prefuncs[k]), sizeof(struct bwb_function));
            memcpy(&(bwb_prefuncs[k]), &t, sizeof(struct bwb_function));
         }
      }
      IsSorted = TRUE;
   }
}


void
DumpAllCommandUniqueID(void)
{
   register int    i;
   char            tbuf[BasicStringLengthMax + 1];

   bwx_DEBUG(__FUNCTION__);

   prn_lprintf("/* COMMANDS */\n");

   /* run through the command table and print comand #define */

   for (i = 0; i < NUM_COMMANDS; i++)
   {
      char           *Syntax;
      char            UniqueID[BasicNameLengthMax + 1];

      CommandUniqueID(i, UniqueID);
      Syntax = bwb_cmdtable[i].name;
      sprintf(tbuf, "#define %-30s %3d /* %-30s */\n", UniqueID, i + 1, Syntax);
      prn_lprintf(tbuf);
   }
   sprintf(tbuf, "#define NUM_COMMANDS %d\n", i);
   prn_lprintf(tbuf);

}

static void
ProcessEscapeChars(const char *Input, char *Output)
{
   int             n;


   bwx_DEBUG(__FUNCTION__);

   n = 0;

   while (*Input != '\0')
   {
      switch (*Input)
      {
      case '\n':
         *Output = '\\';
         Output++;
         *Output = 'n';
         Output++;
         break;
      case '\t':
         *Output = '\\';
         Output++;
         *Output = 't';
         Output++;
         break;
      case '\"':
         *Output = '\\';
         Output++;
         *Output = '"';
         Output++;
         break;
      default:
         *Output = *Input;
         Output++;
         break;
      }
      Input++;
      *Output = '\0';
      n++;
      if (n > 60)
      {
         *Output = '\"';
         Output++;
         *Output = '\n';
         Output++;
         *Output = '\"';
         Output++;
         *Output = '\0';
         n = 0;
      }
   }
}

void
DumpAllCommandTableDefinitions(void)
{
   /* generate bwd_cmd.c */

   int             i;

   bwx_DEBUG(__FUNCTION__);

   prn_lprintf("/* COMMAND TABLE */\n\n");
   prn_lprintf("#include \"bwbasic.h\"\n\n");
   prn_lprintf("struct bwb_command bwb_cmdtable[ NUM_COMMANDS ] =\n");
   prn_lprintf("{\n");

   /* run through the command table and print comand #define */

   for (i = 0; i < NUM_COMMANDS; i++)
   {
      char            tbuf[BasicStringLengthMax + 1];


      prn_lprintf("{\n");

      prn_lprintf("   ");
      CommandUniqueID(i, tbuf);
      prn_lprintf(tbuf);
      prn_lprintf(",  /* UniqueID */\n");

      prn_lprintf("   ");
      prn_lprintf("\"");
      ProcessEscapeChars(bwb_cmdtable[i].Syntax, tbuf);
      prn_lprintf(tbuf);
      prn_lprintf("\"");
      prn_lprintf(",  /* Syntax */\n");

      prn_lprintf("   ");
      prn_lprintf("\"");
      ProcessEscapeChars(bwb_cmdtable[i].Description, tbuf);
      prn_lprintf(tbuf);
      prn_lprintf("\"");
      prn_lprintf(",  /* Description */\n");

      prn_lprintf("   ");
      prn_lprintf("\"");
      prn_lprintf(bwb_cmdtable[i].name);
      prn_lprintf("\"");
      prn_lprintf(",  /* Name */\n");

      prn_lprintf("   ");
      CommandOptionVersion(i, tbuf);
      prn_lprintf(tbuf);
      prn_lprintf("  /* OptionVersionBitmask */\n");

      prn_lprintf("},\n");
   }
   prn_lprintf("};\n\n");

}

void
DumpAllCommandSwitchStatement(void)
{
   int             i;

   bwx_DEBUG(__FUNCTION__);

   /* run through the command table and print comand #define */
   prn_lprintf("/* SWITCH */\n");
   prn_lprintf("struct bwb_line *bwb_vector( struct bwb_line *l )\n");
   prn_lprintf("{\n");

   prn_lprintf("   ");
   prn_lprintf("struct bwb_line *r;\n");

   prn_lprintf("   ");
   prn_lprintf("switch( l->cmdnum )\n");

   prn_lprintf("   ");
   prn_lprintf("{\n");

   for (i = 0; i < NUM_COMMANDS; i++)
   {
      char            tbuf[BasicNameLengthMax + 1];

      prn_lprintf("   ");
      prn_lprintf("case ");
      CommandUniqueID(i, tbuf);
      prn_lprintf(tbuf);
      prn_lprintf(":\n");

      prn_lprintf("   ");
      prn_lprintf("   ");
      prn_lprintf("r = ");
      CommandVector(i, tbuf);
      prn_lprintf(tbuf);
      prn_lprintf("( l );\n");

      prn_lprintf("   ");
      prn_lprintf("   ");
      prn_lprintf("break;\n");

   }

   prn_lprintf("   ");
   prn_lprintf("default:\n");

   prn_lprintf("   ");
   prn_lprintf("   ");
   prn_lprintf("sprintf( bwb_ebuf, \"in bwb_vector(), INTERNAL ERROR: %d not in switch\", l->cmdnum );\n");


   prn_lprintf("   ");
   prn_lprintf("   ");
   prn_lprintf("bwb_error( bwb_ebuf );\n");

   prn_lprintf("   ");
   prn_lprintf("   ");
   prn_lprintf("r = l;\n");

   prn_lprintf("   ");
   prn_lprintf("   ");
   prn_lprintf("break;\n");


   prn_lprintf("   ");
   prn_lprintf("}\n");

   prn_lprintf("   ");
   prn_lprintf("return r;\n");

   prn_lprintf("}\n");

}

void
DumpOneCommandSyntax(int n, int lp)
{
   char            tbuf[BasicStringLengthMax + 1];

   bwx_DEBUG(__FUNCTION__);

   if (n < 0 || n >= NUM_COMMANDS)
   {
      return;
   }
   /* NAME */
   {
      sprintf(tbuf, "     SYNTAX: %s\n", bwb_cmdtable[n].Syntax);
      if (lp)
      {
         prn_lprintf(tbuf);
      }
      else
      {
         prn_xprintf(tbuf);
      }
   }
   /* DESCRIPTION */
   {
      sprintf(tbuf, "DESCRIPTION: %s\n", bwb_cmdtable[n].Description);
      if (lp)
      {
         prn_lprintf(tbuf);
      }
      else
      {
         prn_xprintf(tbuf);
      }
   }
   /* COMPATIBILITY */
   if (lp)
   {
      int             i;
      sprintf(tbuf, "   VERSIONS:\n");
      if (lp)
      {
         prn_lprintf(tbuf);
      }
      else
      {
         prn_xprintf(tbuf);
      }
      for (i = 0; i < NUM_VERSIONS; i++)
      {
         char            X;
         if (bwb_cmdtable[n].OptionVersionBitmask & bwb_vertable[i].OptionVersionBitmask)
         {
            /* SUPPORTED */
            X = 'X';
         }
         else
         {
            /* NOT SUPPORTED */
            X = '_';
         }
         sprintf(tbuf, "             [%c] %s\n", X, bwb_vertable[i].Name);
         if (lp)
         {
            prn_lprintf(tbuf);
         }
         else
         {
            prn_xprintf(tbuf);
         }
      }
   }
}

void
DumpAllCommandSyntax(void)
{
   /* for the C maintainer */
   int             n;


   bwx_DEBUG(__FUNCTION__);

   for (n = 0; n < NUM_COMMANDS; n++)
   {
      prn_lprintf("------------------------------------------------------------\n");
      DumpOneCommandSyntax(n, TRUE);

   }
   prn_lprintf("------------------------------------------------------------\n");
}


void
DumpAllCommandHtmlTable(void)
{
   /* generate bwd_cmd.htm */

   int             i;
   int             j;

   bwx_DEBUG(__FUNCTION__);


   /* LEGEND */
   prn_lprintf("<h1>LEGEND</h1><br>\n");
   prn_lprintf("<table>\n");

   prn_lprintf("<tr>");
   prn_lprintf("<td>");
   prn_lprintf("<b>");
   prn_lprintf("ID");
   prn_lprintf("</b>");
   prn_lprintf("</td>");
   prn_lprintf("<td>");
   prn_lprintf("<b>");
   prn_lprintf("NAME");
   prn_lprintf("</b>");
   prn_lprintf("</td>");
   prn_lprintf("<td>");
   prn_lprintf("<b>");
   prn_lprintf("DESCRIPTION");
   prn_lprintf("</b>");
   prn_lprintf("</td>");
   prn_lprintf("</tr>\n");

   for (j = 0; j < NUM_VERSIONS; j++)
   {
      prn_lprintf("<tr>");
      prn_lprintf("<td>");
      prn_lprintf(bwb_vertable[j].ID);
      prn_lprintf("</td>");
      prn_lprintf("<td>");
      prn_lprintf(bwb_vertable[j].Name);
      prn_lprintf("</td>");
      prn_lprintf("<td>");
      prn_lprintf(bwb_vertable[j].Description);
      prn_lprintf("</td>");
      prn_lprintf("</tr>\n");
   }
   prn_lprintf("</table>\n");
   prn_lprintf("<hr>\n");


   /* DETAILS */
   prn_lprintf("<h1>DETAILS</h1><br>\n");
   prn_lprintf("<table>\n");

   prn_lprintf("<tr>");
   prn_lprintf("<td>");
   prn_lprintf("<b>");
   prn_lprintf("COMMAND");
   prn_lprintf("</b>");
   prn_lprintf("</td>");
   for (j = 0; j < NUM_VERSIONS; j++)
   {
      prn_lprintf("<td>");
      prn_lprintf("<b>");
      prn_lprintf(bwb_vertable[j].ID);
      prn_lprintf("</b>");
      prn_lprintf("</td>");
   }
   prn_lprintf("</tr>\n");


   /* run through the command table and print comand -vs- OPTION VERSION */

   for (i = 0; i < NUM_COMMANDS; i++)
   {
      prn_lprintf("<tr>");
      prn_lprintf("<td>");
      prn_lprintf(bwb_cmdtable[i].name);
      prn_lprintf("</td>");

      for (j = 0; j < NUM_VERSIONS; j++)
      {
         prn_lprintf("<td>");
         if (bwb_cmdtable[i].OptionVersionBitmask & bwb_vertable[j].OptionVersionBitmask)
         {
            prn_lprintf("X");
         }
         else
         {
            prn_lprintf(" ");
         }
         prn_lprintf("</td>");
      }
      prn_lprintf("</tr>\n");
   }
   prn_lprintf("</table>\n");
   prn_lprintf("\n");
}





struct bwb_line *
bwb_HELP(struct bwb_line * l)
{
   /* HELP ... */
   int             n;
   char            tbuf[BasicStringLengthMax + 1];
   int             Found;
   char * C;

   bwx_DEBUG(__FUNCTION__);

   Found = FALSE;

   C = l->buffer;
   C += l->position;
   strcpy( tbuf, C );

   C = strchr( tbuf, OptionCommentChar );
   if( C != NULL )
   {
       *C = '\0';
   }
   /* RTRIM$ */
   C = tbuf;
   if (*C != 0)
   {
      /* not an empty line, so remove one (or more) trailing spaces */
      char           *E;

      E = strchr(tbuf, 0);
      E--;
      while (E >= tbuf && *E == ' ')
      {
         *E = 0;
         E--;
      }
   }
   

   /* EXACT match */
   for (n = 0; n < NUM_COMMANDS; n++)
   {
      if (strcasecmp(bwb_cmdtable[n].name, tbuf) == 0)
      {
         prn_xprintf("------------------------------------------------------------\n");
         DumpOneCommandSyntax(n, FALSE);
         Found = TRUE;
      }
   }

   for (n = 0; n < NUM_FUNCTIONS; n++)
   {
      if (strcasecmp(bwb_prefuncs[n].Name, tbuf) == 0)
      {
         prn_xprintf("------------------------------------------------------------\n");
         DumpOneFunctionSyntax(n, FALSE);
         Found = TRUE;
      }
   }

   if (Found == FALSE)
   {
      /* PARTIAL match */
      int             Length;
      Length = strlen(tbuf);

      for (n = 0; n < NUM_COMMANDS; n++)
      {
         if (strncasecmp(bwb_cmdtable[n].name, tbuf, Length) == 0)
         {
            if (Found == FALSE)
            {
               prn_xprintf("The following topics are a partial match:\n");
            }
            prn_xprintf(bwb_cmdtable[n].name);
            prn_xprintf("\t");
            Found = TRUE;
         }
      }

      for (n = 0; n < NUM_FUNCTIONS; n++)
      {
         if (strncasecmp(bwb_prefuncs[n].Name, tbuf, Length) == 0)
         {
            if (Found == FALSE)
            {
               prn_xprintf("The following topics are a partial match:\n");
            }
            prn_xprintf(bwb_prefuncs[n].Name);
            prn_xprintf("\t");
            Found = TRUE;
         }
      }
      if (Found == TRUE)
      {
         /* match */
         prn_xprintf("\n");
      }
   }
   if (Found == FALSE)
   {
      /* NO match */
      prn_xprintf("No help found.\n");
   }
   adv_eos(l->buffer, &(l->position));
   return bwb_zline(l);

}

int
NumberValueCheck(unsigned long ParameterTests, BasicNumberType X)
{
   BasicNumberType XR;  /* rounded value */


   bwx_DEBUG(__FUNCTION__);


   /* check for invalid numeric value */
   if (isnan(X))
   {
      /* INTERNAL ERROR */
      return -1;
   }
   /* VALID NUMERIC VALUE */
   XR = rint(X);
   ParameterTests &= 0x0000000F;
   switch (ParameterTests)
   {
   case P1ERR:
      {
         /* INTERNAL ERROR */
         return -1;
      }
      break;
   case P1ANY:
      if (XR < -DBL_MAX || XR > DBL_MAX)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
   case P1BYT:
      if (XR < 0 || XR > UCHAR_MAX)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1INT:
      if (XR < SHRT_MIN || XR > SHRT_MAX)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1LNG:
      if (XR < LONG_MIN || XR > LONG_MAX)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1CUR:
      if (XR < LONG_MIN || XR > LONG_MAX)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1FLT:
      if (X < -FLT_MAX || X > FLT_MAX)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1DBL:
      if (X < -DBL_MAX || X > DBL_MAX)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1DEV:
      /* BasicFileNumberMax must be <= INT16_MAX */
      if (XR < 0 || XR > BasicFileNumberMax)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1LEN:
      /* BasicStringLengthMax must be <= INT16_MAX */
      if (XR < 0 || XR > BasicStringLengthMax)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1POS:
      /* BasicStringLengthMax must be <= INT16_MAX */
      if (XR < 1 || XR > BasicStringLengthMax)
      {
         /* ERROR */
         return -1;
      }
      else
      {
         /* OK */
         return 0;
      }
      break;
   case P1COM:
      /* Number of COMx: ports must be <= INT16_MAX */
      if (XR == 1 || XR == 2 || XR == 3 || XR == 4)
      {
         /* OK */
         return 0;
      }
      break;
   case P1LPT:
      /* Number of LPTx: ports must be <= INT16_MAX */
      if (XR == 0 || XR == 1 || XR == 2 || XR == 3)
      {
         /* OK */
         return 0;
      }
      break;
   case P1GTZ:
      if (X > 0)
      {
         /* OK */
         return 0;
      }
      break;
   case P1GEZ:
      if (X >= 0)
      {
         /* OK */
         return 0;
      }
      break;
   case P1NEZ:
      if (X != 0)
      {
         /* OK */
         return 0;
      }
      break;
   }
   /* ERROR */
   return -1;
}

int
StringLengthCheck(unsigned long ParameterTests, int s)
{

   bwx_DEBUG(__FUNCTION__);

   /* check for invalid string length */
   if (s < 0 || s > BasicStringLengthMax)
   {
      /* INTERNAL ERROR */
      return -1;
   }
   /* VALID STRING LENGTH */
   ParameterTests &= 0x0000000F;

   switch (ParameterTests)
   {
   case P1ERR:
      {
         /* INTERNAL ERROR */
         return -1;
      }
      break;
   case P1ANY:
      {
         /* OK */
         return 0;
      }
      break;
   case P1BYT:
      if (s >= sizeof(BasicByteType))
      {
         /* OK */
         return 0;
      }
      break;
   case P1INT:
      if (s >= sizeof(BasicIntegerType))
      {
         /* OK */
         return 0;
      }
      break;
   case P1LNG:
      if (s >= sizeof(BasicLongType))
      {
         /* OK */
         return 0;
      }
      break;
   case P1CUR:
      if (s >= sizeof(BasicCurrencyType))
      {
         /* OK */
         return 0;
      }
      break;
   case P1FLT:
      if (s >= sizeof(BasicSingleType))
      {
         /* OK */
         return 0;
      }
      break;
   case P1DBL:
      if (s >= sizeof(BasicDoubleType))
      {
         /* OK */
         return 0;
      }
      break;
   case P1DEV:
   case P1LEN:
   case P1POS:
   case P1GEZ:
   case P1GTZ:
   case P1NEZ:
      {
         /* ERROR */
         return -1;
      }
      break;
   }
   /* ERROR */
   return -1;
}

void
FunctionDefinitionCheck(struct bwb_function * f)
{
   /* function definition check -- look for obvious errors */
   char            bwb_ebuf[BasicStringLengthMax + 1];

   bwx_DEBUG(__FUNCTION__);


   /* sanity check */
   if (f->ParameterCount == PNONE)
   {
      /* function has NO parameters */
      if (f->ParameterTypes == PNONE)
      {
         /* OK */
      }
      else
      {
         /* oops */
         sprintf(bwb_ebuf, "in fnc_init(): invalid ParameterTypes <%s>\n", f->Name);
         prn_xprintf(bwb_ebuf);
      }
      if (f->ParameterTests == PNONE)
      {
         /* OK */
      }
      else
      {
         /* oops */
         sprintf(bwb_ebuf, "in fnc_init(): invalid ParameterTests <%s>\n", f->Name);
         prn_xprintf(bwb_ebuf);
      }
   }
   else
   {
      /* function HAS parameters */
      int             i;
      unsigned long   ParameterTests;
      ParameterTests = f->ParameterTests;
      for (i = 0; i < f->ParameterCount; i++)
      {
         /* sanity check this parameter */
         unsigned long   thischeck;
         thischeck = ParameterTests & 0x0000000F;
         /* verify parameter check  */
         if (f->ParameterTypes & (1 << i))
         {
            /* STRING */
            if (thischeck >= P1ANY && thischeck <= P1DBL)
            {
               /* OK */
            }
            else
            {
               /* oops */
               sprintf(bwb_ebuf, "in fnc_init(): invalid ParameterTests <%s> parameter %d\n", f->Name, i + 1);
               prn_xprintf(bwb_ebuf);
            }
         }
         else
         {
            /* NUMBER */
            if (thischeck >= P1ANY && thischeck <= P1NEZ)
            {
               /* OK */
            }
            else
            {
               /* oops */
               sprintf(bwb_ebuf, "in fnc_init(): invalid ParameterTests <%s> parameter %d\n", f->Name, i + 1);
               prn_xprintf(bwb_ebuf);
            }
         }
         ParameterTests = ParameterTests >> 4;
      }
      if (ParameterTests != 0)
      {
         /* oops */
         sprintf(bwb_ebuf, "in fnc_init(): invalid ParameterTests <%s> parameter %d\n", f->Name, i + 1);
         prn_xprintf(bwb_ebuf);
      }
   }
}

void
FunctionUniqueID(struct bwb_function * f, char *UniqueID)
{
   /* generate the function's UniqueID */
   /* manual fixup required for duplicates */
   char           *D;   /* location of $ */
   char            NumVar;
   char            StrVar;

   bwx_DEBUG(__FUNCTION__);


   NumVar = 'X';
   StrVar = 'A';

   /* F_  */
   strcpy(UniqueID, "F_");
   /* NAME  */
   strcat(UniqueID, f->Name);
   D = strchr(UniqueID, BasicStringSuffix);
   if (D == NULL)
   {
      /* NOT FOUND */
   }
   else
   {
      /* FOUND */
      *D = 0;     /* remove it */
   }
   /* PARAMETERS */
   if (f->ParameterCount == PNONE)
   {
      /* function has NO parameters */
   }
   else
   {
      /* function HAS parameters */
      int             i;
      unsigned int    ParameterTypes;
      /* unsigned long ParameterTests; */
      ParameterTypes = f->ParameterTypes;
      /* ParameterTests = f->ParameterTests; */
      for (i = 0; i < f->ParameterCount; i++)
      {
         char            VarName[BasicNameLengthMax + 1];
         /* sanity check this parameter */
         /* unsigned long thischeck; thischeck =
          * ParameterTests & 0x0000000F; */
         /* verify parameter check  */
         if (ParameterTypes & 1)
         {
            /* STRING */
            sprintf(VarName, "_%c", StrVar);
            StrVar++;
         }
         else
         {
            /* NUMBER */
            sprintf(VarName, "_%c", NumVar);
            NumVar++;
         }
         strcat(UniqueID, VarName);
         ParameterTypes = ParameterTypes >> 1;
         /* ParameterTests = ParameterTests >> 4; */
      }
   }
   /* RETURN TYPE */
   switch (f->ReturnType)
   {
   case STRING:
      strcat(UniqueID, "_S");
      break;
   case NUMBER:
      strcat(UniqueID, "_N");
      break;
   default:
      strcat(UniqueID, "_INTERNAL ERROR");
      break;
   }
   /* fixup illegal characters,  "DEF FN" "BLOAD:", "CLOAD*" */
   {
      char           *P;
      P = UniqueID;
      while (*P)
      {
         if (isalnum(*P))
         {
            /* OK */
         }
         else
         {
            /* FIXUP */
            *P = '_';
         }
         P++;
      }
   }
}


void
FunctionSyntax(struct bwb_function * f, char *Syntax)
{
   /* generate the function's Syntax */
   char            NumVar;
   char            StrVar;

   bwx_DEBUG(__FUNCTION__);


   NumVar = 'X';
   StrVar = 'A';

   /* RETURN TYPE */
   switch (f->ReturnType)
   {
   case STRING:
      strcpy(Syntax, "S$ = ");
      break;
   case NUMBER:
      strcpy(Syntax, "N  = ");
      break;
   default:
      strcpy(Syntax, "INTERNAL ERROR = ");
      break;
   }
   /* NAME  */
   strcat(Syntax, f->Name);
   /* PARAMETERS */
   if (f->ParameterCount == PNONE)
   {
      /* function has NO parameters */
   }
   else
   {
      /* function HAS parameters */
      int             i;
      unsigned int    ParameterTypes;
      /* unsigned long ParameterTests; */
      ParameterTypes = f->ParameterTypes;
      /* ParameterTests = f->ParameterTests; */

      switch (f->ReturnType)
      {
      case STRING:
         strcat(Syntax, "( ");
         break;
      case NUMBER:
         strcat(Syntax, "( ");
         break;
      default:
         strcat(Syntax, "INTERNAL ERROR( ");
         break;
      }

      for (i = 0; i < f->ParameterCount; i++)
      {
         char            VarName[BasicNameLengthMax + 1];
         /* sanity check this parameter */
         /* unsigned long thischeck; thischeck =
          * ParameterTests & 0x0000000F; */
         if (i > 0)
         {
            strcat(Syntax, ", ");
         }
         /* verify parameter check  */
         if (ParameterTypes & 1)
         {
            /* STRING */
            sprintf(VarName, "%c$", StrVar);
            StrVar++;
         }
         else
         {
            /* NUMBER */
            sprintf(VarName, "%c", NumVar);
            NumVar++;
         }
         strcat(Syntax, VarName);
         ParameterTypes = ParameterTypes >> 1;
         /* ParameterTests = ParameterTests >> 4; */
      }
      switch (f->ReturnType)
      {
      case STRING:
         strcat(Syntax, " )");
         break;
      case NUMBER:
         strcat(Syntax, " )");
         break;
      default:
         strcat(Syntax, " INTERNAL ERROR)");
         break;
      }
   }
}

void
DumpAllFuctionUniqueID(void)
{
   /* for the C maintainer */
   char            tbuf[BasicStringLengthMax + 1];
   int             i;

   bwx_DEBUG(__FUNCTION__);

   prn_lprintf("/* FUNCTIONS */\n");
   for (i = 0; i < NUM_FUNCTIONS; i++)
   {
      char            UniqueID[BasicStringLengthMax + 1];
      char            Syntax[BasicStringLengthMax + 1];

      FunctionUniqueID(&(bwb_prefuncs[i]), UniqueID);
      FunctionSyntax(&(bwb_prefuncs[i]), Syntax);
      sprintf(tbuf, "#define %-30s %3d /* %-30s */\n", UniqueID, i + 1, Syntax);
      prn_lprintf(tbuf);
   }
   sprintf(tbuf, "#define NUM_FUNCTIONS %d\n", i);
   prn_lprintf(tbuf);
}

void
DumpAllFunctionSwitch(void)
{
   /* for the C maintainer */
   int             i;

   bwx_DEBUG(__FUNCTION__);

   prn_lprintf("/* SWITCH */\n");
   prn_lprintf("switch( UniqueID )\n");
   prn_lprintf("{\n");
   for (i = 0; i < NUM_FUNCTIONS; i++)
   {
      char            tbuf[BasicStringLengthMax + 1];

      prn_lprintf("case ");
      FunctionUniqueID(&(bwb_prefuncs[i]), tbuf);
      prn_lprintf(tbuf);
      prn_lprintf(":\n");
      prn_lprintf("    break;\n");
   }
   prn_lprintf("}\n");
}

static const char *ParameterRangeID[16] =
{
 "P%dERR",
 "P%dANY",
 "P%dBYT",
 "P%dINT",
 "P%dLNG",
 "P%dCUR",
 "P%dFLT",
 "P%dDBL",
 "P%dDEV",
 "P%dLEN",
 "P%dPOS",
 "P%dCOM",
 "P%dLPT",
 "P%dGTZ",
 "P%dGEZ",
 "P%dNEZ",
};

static const char *NumberVariableRange[16] =
{
  /* P1ERR */ "  PARAMETER: %c  is a number, INTERNAL ERROR",
  /* P1ANY */ "  PARAMETER: %c  is a number",
  /* P1BYT */ "  PARAMETER: %c  is a number, [0,255]",
  /* P1INT */ "  PARAMETER: %c  is a number, [MININT,MAXINT]",
  /* P1LNG */ "  PARAMETER: %c  is a number, [MINLNG,MAXLNG]",
  /* P1CUR */ "  PARAMETER: %c  is a number, [MINCUR,MAXCUR]",
  /* P1FLT */ "  PARAMETER: %c  is a number, [MINFLT,MAXFLT]",
  /* P1DBL */ "  PARAMETER: %c  is a number, [MINDBL,MAXDBL]",
  /* P1DEV */ "  PARAMETER: %c  is a number, [1,MAXDEV]",
  /* P1LEN */ "  PARAMETER: %c  is a number, [0,MAXLEN]",
  /* P1POS */ "  PARAMETER: %c  is a number, [1,MAXLEN]",
  /* P1COM */ "  PARAMETER: %c  is a number, [1,4]",
  /* P1LPT */ "  PARAMETER: %c  is a number, [0,3]",
  /* P1GTZ */ "  PARAMETER: %c  is a number, >  0",
  /* P1GEZ */ "  PARAMETER: %c  is a number, >= 0",
  /* P1NEZ */ "  PARAMETER: %c  is a number, <> 0",
};

static const char *StringVariableRange[16] =
{
  /* P1ERR */ "  PARAMETER: %c$ is a string, INTERNAL ERROR",
  /* P1ANY */ "  PARAMETER: %c$ is a string, LEN >= 0",
  /* P1BYT */ "  PARAMETER: %c$ is a string, LEN >= 1",
  /* P1INT */ "  PARAMETER: %c$ is a string, LEN >= sizeof(INT)",
  /* P1LNG */ "  PARAMETER: %c$ is a string, LEN >= sizeof(LNG)",
  /* P1CUR */ "  PARAMETER: %c$ is a string, LEN >= sizeof(CUR)",
  /* P1FLT */ "  PARAMETER: %c$ is a string, LEN >= sizeof(FLT)",
  /* P1DBL */ "  PARAMETER: %c$ is a string, LEN >= sizeof(DBL)",
  /* P1DEV */ "  PARAMETER: %c$ is a string, RESERVED",
  /* P1LEN */ "  PARAMETER: %c$ is a string, RESERVED",
  /* P1POS */ "  PARAMETER: %c$ is a string, RESERVED",
  /* P1COM */ "  PARAMETER: %c$ is a string, RESERVED",
  /* P1LPT */ "  PARAMETER: %c$ is a string, RESERVED",
  /* P1GTZ */ "  PARAMETER: %c$ is a string, RESERVED",
  /* P1GEZ */ "  PARAMETER: %c$ is a string, RESERVED",
  /* P1NEZ */ "  PARAMETER: %c$ is a string, RESERVED",
};

void
DumpAllFuctionTableDefinitions(void)
{
   /* generate bwd_fun.c */
   int             n;

   bwx_DEBUG(__FUNCTION__);


   prn_lprintf("/* FUNCTION TABLE */\n\n");
   prn_lprintf("#include \"bwbasic.h\"\n\n");
   prn_lprintf("struct bwb_function bwb_prefuncs[ NUM_FUNCTIONS ] =\n");
   prn_lprintf("{\n");
   for (n = 0; n < NUM_FUNCTIONS; n++)
   {
      int             i;
      int             j;
      char            tbuf[BasicStringLengthMax + 1];
      char            UniqueID[BasicStringLengthMax + 1];
      char            Syntax[BasicStringLengthMax + 1];
      struct bwb_function *f;

      f = &(bwb_prefuncs[n]);

      FunctionUniqueID(f, UniqueID);
      FunctionSyntax(f, Syntax);
      prn_lprintf("{\n");
      sprintf(tbuf, "  %s, /* UniqueID */\n", UniqueID);
      prn_lprintf(tbuf);
      sprintf(tbuf, "  \"%s\", /* Syntax */\n", Syntax);
      prn_lprintf(tbuf);

      prn_lprintf("  ");
      prn_lprintf("\"");
      ProcessEscapeChars(f->Description, tbuf);
      prn_lprintf(tbuf);
      prn_lprintf("\"");
      prn_lprintf(",  /* Description */\n");


      sprintf(tbuf, "  \"%s\", /* Name */\n", f->Name);
      prn_lprintf(tbuf);
      switch (f->ReturnType)
      {
      case STRING:
         sprintf(tbuf, "  %s, /* ReturnType */\n", "STRING");
         break;
      case NUMBER:
         sprintf(tbuf, "  %s, /* ReturnType */\n", "NUMBER");
         break;
      default:
         sprintf(tbuf, "  %s, /* ReturnType */\n", "INTERNAL ERROR");
         break;
      }
      prn_lprintf(tbuf);
      sprintf(tbuf, "  %d, /* ParameterCount */\n", f->ParameterCount);
      prn_lprintf(tbuf);
      if (f->ParameterCount == 0)
      {
         sprintf(tbuf, "  %s, /* ParameterTypes */\n", "PNONE");
         prn_lprintf(tbuf);
         sprintf(tbuf, "  %s, /* ParameterTests */\n", "PNONE");
         prn_lprintf(tbuf);
      }
      else
      {
         strcpy(tbuf, "  ");
         for (i = 0; i < f->ParameterCount; i++)
         {
            int             ParameterTypes;
            ParameterTypes = f->ParameterTypes >> i;
            ParameterTypes &= 0x1;
            if (i > 0)
            {
               strcat(tbuf, " | ");
            }
            if (ParameterTypes)
            {
               sprintf(strchr(tbuf, 0), "P%dSTR", i + 1);
            }
            else
            {
               sprintf(strchr(tbuf, 0), "P%dNUM", i + 1);
            }
         }
         strcat(tbuf, ", /* ParameterTypes */\n");
         prn_lprintf(tbuf);


         strcpy(tbuf, "  ");
         for (i = 0; i < f->ParameterCount; i++)
         {
            unsigned int    ParameterTests;
            ParameterTests = f->ParameterTests >> (i * 4);
            ParameterTests &= 0xF;

            if (i > 0)
            {
               strcat(tbuf, " | ");
            }
            sprintf(strchr(tbuf, 0), ParameterRangeID[ParameterTests], i + 1);
         }
         strcat(tbuf, ", /* ParameterTests */\n");
         prn_lprintf(tbuf);
      }
      sprintf(tbuf, "  %s, /* NextPointer */\n", "NULL");
      prn_lprintf(tbuf);
      strcpy(tbuf, "  ");
      j = 0;
      for (i = 0; i < NUM_VERSIONS; i++)
      {
         if (f->OptionVersionBitmask & bwb_vertable[i].OptionVersionBitmask)
         {
            if (j > 0)
            {
               strcat(tbuf, " | ");
            }
            strcat(tbuf, bwb_vertable[i].ID);
            j++;
         }
      }
      strcat(tbuf, " /* OptionVersionBitmask */\n");
      prn_lprintf(tbuf);
      prn_lprintf("},\n");
   }
   prn_lprintf("};\n\n");
}




void
DumpOneFunctionSyntax(int n, int lp)
{
   char            tbuf[BasicStringLengthMax + 1];
   struct bwb_function *f;

   bwx_DEBUG(__FUNCTION__);

   if (n < 0 || n >= NUM_FUNCTIONS)
   {
      return;
   }
   f = &(bwb_prefuncs[n]);
   /* NAME */
   {
      char            UniqueID[BasicStringLengthMax + 1];
      char            Syntax[BasicStringLengthMax + 1];

      FunctionUniqueID(f, UniqueID);
      FunctionSyntax(f, Syntax);
      sprintf(tbuf, "     SYNTAX: %s\n", Syntax);
      if (lp)
      {
         prn_lprintf(tbuf);
      }
      else
      {
         prn_xprintf(tbuf);
      }
   }
   /* PARAMETERS */
   if (f->ParameterCount == PNONE)
   {
      /* function has NO parameters */
   }
   else
   {
      /* function HAS parameters */
      int             i;
      unsigned int    ParameterTypes;
      unsigned long   ParameterTests;
      char            NumVar;
      char            StrVar;
      ParameterTypes = f->ParameterTypes;
      ParameterTests = f->ParameterTests;
      NumVar = 'X';
      StrVar = 'A';
      for (i = 0; i < f->ParameterCount; i++)
      {
         /* sanity check this parameter */
         unsigned long   thischeck;
         thischeck = ParameterTests & 0x0000000F;
         /* verify parameter check  */
         if (ParameterTypes & 1)
         {
            /* STRING */
            sprintf(tbuf, StringVariableRange[thischeck], StrVar);
            StrVar++;
         }
         else
         {
            /* NUMBER */
            sprintf(tbuf, NumberVariableRange[thischeck], NumVar);
            NumVar++;
         }
         if (lp)
         {
            prn_lprintf(tbuf);
         }
         else
         {
            prn_xprintf(tbuf);
         }
         if (lp)
         {
            prn_lprintf("\n");
         }
         else
         {
            prn_xprintf("\n");
         }
         ParameterTypes = ParameterTypes >> 1;
         ParameterTests = ParameterTests >> 4;
      }
   }
   /* DESCRIPTION */
   {
      sprintf(tbuf, "DESCRIPTION: %s\n", f->Description);
      if (lp)
      {
         prn_lprintf(tbuf);
      }
      else
      {
         prn_xprintf(tbuf);
      }
   }
   /* COMPATIBILITY */
   if (lp)
   {
      int             i;
      sprintf(tbuf, "   VERSIONS:\n");
      if (lp)
      {
         prn_lprintf(tbuf);
      }
      else
      {
         prn_xprintf(tbuf);
      }
      for (i = 0; i < NUM_VERSIONS; i++)
      {
         char            X;
         if (f->OptionVersionBitmask & bwb_vertable[i].OptionVersionBitmask)
         {
            /* SUPPORTED */
            X = 'X';
         }
         else
         {
            /* NOT SUPPORTED */
            X = '_';
         }
         sprintf(tbuf, "             [%c] %s\n", X, bwb_vertable[i].Name);
         if (lp)
         {
            prn_lprintf(tbuf);
         }
         else
         {
            prn_xprintf(tbuf);
         }
      }
   }
}

void
DumpAllFuctionSyntax(void)
{
   /* for the C maintainer */
   int             n;

   bwx_DEBUG(__FUNCTION__);

   for (n = 0; n < NUM_FUNCTIONS; n++)
   {
      prn_lprintf("------------------------------------------------------------\n");
      DumpOneFunctionSyntax(n, TRUE);
   }
   prn_lprintf("------------------------------------------------------------\n");
}

void
DumpAllFunctionHtmlTable(void)
{
   /* generate bwd_cmd.htm */

   int             i;
   int             j;

   bwx_DEBUG(__FUNCTION__);


   /* LEGEND */
   prn_lprintf("<h1>LEGEND</h1><br>\n");
   prn_lprintf("<table>\n");

   prn_lprintf("<tr>");
   prn_lprintf("<td>");
   prn_lprintf("<b>");
   prn_lprintf("ID");
   prn_lprintf("</b>");
   prn_lprintf("</td>");
   prn_lprintf("<td>");
   prn_lprintf("<b>");
   prn_lprintf("NAME");
   prn_lprintf("</b>");
   prn_lprintf("</td>");
   prn_lprintf("<td>");
   prn_lprintf("<b>");
   prn_lprintf("DESCRIPTION");
   prn_lprintf("</b>");
   prn_lprintf("</td>");
   prn_lprintf("</tr>\n");

   for (j = 0; j < NUM_VERSIONS; j++)
   {
      prn_lprintf("<tr>");
      prn_lprintf("<td>");
      prn_lprintf(bwb_vertable[j].ID);
      prn_lprintf("</td>");
      prn_lprintf("<td>");
      prn_lprintf(bwb_vertable[j].Name);
      prn_lprintf("</td>");
      prn_lprintf("<td>");
      prn_lprintf(bwb_vertable[j].Description);
      prn_lprintf("</td>");
      prn_lprintf("</tr>\n");
   }
   prn_lprintf("</table>\n");
   prn_lprintf("<hr>\n");


   /* DETAILS */
   prn_lprintf("<h1>DETAILS</h1><br>\n");
   prn_lprintf("<table>\n");

   prn_lprintf("<tr>");
   prn_lprintf("<td>");
   prn_lprintf("<b>");
   prn_lprintf("FUNCTION");
   prn_lprintf("</b>");
   prn_lprintf("</td>");
   for (j = 0; j < NUM_VERSIONS; j++)
   {
      prn_lprintf("<td>");
      prn_lprintf("<b>");
      prn_lprintf(bwb_vertable[j].ID);
      prn_lprintf("</b>");
      prn_lprintf("</td>");
   }
   prn_lprintf("</tr>\n");


   /* run through the command table and print comand -vs- OPTION VERSION */

   for (i = 0; i < NUM_FUNCTIONS; i++)
   {
      prn_lprintf("<tr>");
      prn_lprintf("<td>");
      prn_lprintf(bwb_prefuncs[i].Name);
      prn_lprintf("</td>");

      for (j = 0; j < NUM_VERSIONS; j++)
      {
         prn_lprintf("<td>");
         if (bwb_prefuncs[i].OptionVersionBitmask & bwb_vertable[j].OptionVersionBitmask)
         {
            prn_lprintf("X");
         }
         else
         {
            prn_lprintf(" ");
         }
         prn_lprintf("</td>");
      }
      prn_lprintf("</tr>\n");
   }
   prn_lprintf("</table>\n");
   prn_lprintf("\n");
}

struct bwb_line *
bwb_DEF_SUB(struct bwb_line * l)
{
   /* user is executing a function, such as 100 COS X, as though it were
    * a command */
   /* this applies to both intrinsic functions and user defined
    * functions and subroutines */
   /* no special parsing is required, just add () around the parameters */

   char            Buffer[BasicStringLengthMax + 1];
   int             i;
   char           *C;
   char           *F;

   bwx_DEBUG(__FUNCTION__);

   i = 0;
   C = l->buffer;
   C += l->position;
   F = Buffer;

   while (*C == ' ')
   {
      /* skip leading spaces before name */
      C++;     
   }
   /* these should not happen */
   if (*C == '\0' || *C == OptionCommentChar)
   {
      bwb_error("bwb_DEF_SUB, INTERNAL null");
      return bwb_zline(l);
   }
   if (!isalpha(*C))
   {
      bwb_error("bwb_DEF_SUB, INTERNAL isalpha");
      return bwb_zline(l);
   }
   if (strlen(C) + 2 > BasicStringLengthMax)
   {
      bwb_error("Ibwb_DEF_SUB, NTERNAL strlen");
      return bwb_zline(l);
   }
   /* OK */
   while (isalpha(*C))
   {
      /* copy command name */
      *F = *C;
      F++;
      C++;
   }
   if (*C == BasicStringSuffix)
   {
      /* copy command name */
      *F = *C;
      F++;
      C++;
   }
   while (*C == ' ')
   {
      /* skip trailing spaces after name */
      C++;     
   }
   /* left paren */
   *F = '(';
   F++;
   while (*C != '\0')
   {
      /* copy parameters */
      if( *C == '"' )
      {
          /* quoted string */
          {
              /* leading quote */
              *F = *C;
              F++;
              C++;
          }
          while( *C != '\0' && *C != '"' )
          {
              /* copy inclosed string */
              *F = *C;
              F++;
              C++;
          }
          if( *C == '"' )
          {
              /* trailing quote */
              *F = *C;
              F++;
              C++;
          }
      }
      else
      if( *C == OptionCommentChar )
      {
          /* comment */
          while( *C != '\0' )
          {
              /* skip to end of line */
              C++;
          }
      }
      else
      if( *C == ' ' )
      {
          /* skip unquoted spaces */
          C++;
      }
      else
      {
          /* normal char, so copy it */
          *F = *C;
          F++;
          C++;
      }
   }
   /* right paren */
   *F = ')';
   F++;
   /* terminate */
   *F = '\0';
   /* Call the expression interpreter to evaluate the function */

   /* prn_xprintf(Buffer); */
   bwb_exp(Buffer, FALSE, &i);
   if (ERROR_PENDING)
   {
      /* prn_xprintf("oops"); */
   }
   adv_eos(l->buffer, &(l->position));
   return bwb_zline(l);
}



/***************************************************************
  
        FUNCTION:       bwb_zline()
  
        DESCRIPTION: This function is called at the exit from
            Bywater BASIC command functions.  It returns
            a pointer to the current position in the current line.
  
***************************************************************/

struct bwb_line *
bwb_zline(struct bwb_line * l)
{
   bwx_DEBUG(__FUNCTION__);

   /* skip trailing spaces */
   while (l->buffer[l->position] == ' ')
      l->position++;
   /* skip trailing comment */
   if (l->buffer[l->position] == OptionCommentChar)
   {
      /* COMMENT */
      while (l->buffer[l->position] != 0)
         l->position++;
   }
   /* usually, but NOT always, we are at the end of the line */
   return l;
}


/***************************************************************
  
        FUNCTION:       fnc_fncs()
  
        DESCRIPTION:    This C function is used for debugging
                        purposes; it prints a list of all defined
                        functions.
  
   SYNTAX:     FNCS
  
***************************************************************/
struct bwb_line *
bwb_FNCS(struct bwb_line * l)
{

   register int    n;
   int             t;

   bwx_DEBUG(__FUNCTION__);

   prn_iprintf("BWBASIC FUNCTIONS AVAILABLE:\n");

   /* run through the command table and print comand names */

   t = 0;
   for (n = 0; n < NUM_FUNCTIONS; ++n)
   {
      prn_iprintf(bwb_prefuncs[n].Name);
      if (t < 4)
      {
         prn_iprintf("\t");
         t++;
      }
      else
      {
         prn_iprintf("\n");
         t = 0;
      }
   }
   if (t > 0)
   {
      prn_iprintf("\n");
   }
   return bwb_zline(l);
}


struct bwb_line *
bwb_MAINTAINER(struct bwb_line * l)
{

   int             ShowHelp;
   char            tbuf[BasicStringLengthMax + 1];

   bwx_DEBUG(__FUNCTION__);

   ShowHelp = TRUE;
   adv_element(l->buffer, &(l->position), tbuf);

   if (strcasecmp(tbuf, "CMDS") == 0)
   {

      adv_element(l->buffer, &(l->position), tbuf);

      if (strcasecmp(tbuf, "HTML") == 0)
      {
         DumpAllCommandHtmlTable();
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "ID") == 0)
      {
         DumpAllCommandUniqueID();
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "MANUAL") == 0)
      {
         DumpAllCommandSyntax();
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "SWITCH") == 0)
      {
         DumpAllCommandSwitchStatement();
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "TABLE") == 0)
      {
         DumpAllCommandTableDefinitions();
         ShowHelp = FALSE;
      }
   }
   else
   if (strcasecmp(tbuf, "FNCS") == 0)
   {
      adv_element(l->buffer, &(l->position), tbuf);

      if (strcasecmp(tbuf, "HTML") == 0)
      {
         DumpAllFunctionHtmlTable();
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "ID") == 0)
      {
         DumpAllFuctionUniqueID();
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "MANUAL") == 0)
      {
         DumpAllFuctionSyntax();
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "SWITCH") == 0)
      {
         DumpAllFunctionSwitch();
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "TABLE") == 0)
      {
         DumpAllFuctionTableDefinitions();
         ShowHelp = FALSE;
      }
   }
   else
   if (strcasecmp(tbuf, "DEBUG") == 0)
   {
      adv_element(l->buffer, &(l->position), tbuf);
      if (strcasecmp(tbuf, "ON") == 0)
      {
         MaintainerDebugOn = TRUE;
         ShowHelp = FALSE;
      }
      else
      if (strcasecmp(tbuf, "OFF") == 0)
      {
         MaintainerDebugOn = FALSE;
         ShowHelp = FALSE;
      }
   }
   else
   if (strcasecmp(tbuf, "STACK") == 0)
   {
      /* * dump the current execution stack, * Leftmost is the
       * bottom, * Rigthmost is the top. */
      int             i;
      for (i = 0; i <= CURTASK exsc; i++)
      {
         struct bwb_line *l;

         l = CURTASK excs[i].line;
         if (l != NULL)
         {
            char            LineNumber[32];

            sprintf(LineNumber, "%d:", l->number);
            prn_xprintf(LineNumber);
         }
      }
      prn_xprintf("\n");
      ShowHelp = FALSE;
   }
   if (ShowHelp == TRUE)
   {
      prn_xprintf("MAINTAINER is a command intended for the C maintainer.\n");
      prn_xprintf("It is used when making global changes to the internal tables.\n");
      prn_xprintf("This command is subject to change without notice, at any time.\n");
      prn_xprintf("Currently, the valid statements are:\n\n");
      prn_xprintf("MAINTAINER CMDS HTML   ' dump COMMAND vs VERSION as HTML table\n");
      prn_xprintf("MAINTAINER CMDS ID     ' dump COMMAND #defines for UniqueID\n");
      prn_xprintf("MAINTAINER CMDS MANUAL ' dump COMMAND manual\n");
      prn_xprintf("MAINTAINER CMDS SWITCH ' dump COMMAND switch(UniqueID)\n");
      prn_xprintf("MAINTAINER CMDS TABLE  ' dump COMMAND table\n");
      prn_xprintf("MAINTAINER FNCS HTML   ' dump FUNCTION vs VERSION as HTML table\n");
      prn_xprintf("MAINTAINER FNCS ID     ' dump FUNCTION #defines for UniqueID\n");
      prn_xprintf("MAINTAINER FNCS MANUAL ' dump FUNCTION manual\n");
      prn_xprintf("MAINTAINER FNCS SWITCH ' dump FUNCTION switch(UniqueID)\n");
      prn_xprintf("MAINTAINER FNCS TABLE  ' dump FUNCTION table\n");
      prn_xprintf("MAINTAINER DEBUG ON    ' enable degug tracing\n");
      prn_xprintf("MAINTAINER DEBUG OFF   ' disable degug tracing\n");
      prn_xprintf("MAINTAINER STACK       ' dump the BASIC stack\n");
   }
   return bwb_zline(l);
}


/***************************************************************
  
        FUNCTION:       fnc_init()
  
        DESCRIPTION:    This command initializes the function
                        linked list, placing all predefined functions
                        in the list.
  
***************************************************************/

int
fnc_init(int task)
{
   register int    n;
   struct bwb_function *f;

   bwx_DEBUG(__FUNCTION__);

   strcpy(LOCALTASK fnc_start.Name, "FNC_START");
   LOCALTASK       fnc_start.ReturnType = 'X';
   strcpy(LOCALTASK fnc_end.Name, "FNC_END");
   LOCALTASK       fnc_end.ReturnType = 'x';
   LOCALTASK       fnc_end.next = &LOCALTASK fnc_end;


   f = &LOCALTASK fnc_start;

   /* now go through each of the preestablished functions and set up
    * links between them; from this point the program address the
    * functions only as a linked list (not as an array) */

   for (n = 0; n < NUM_FUNCTIONS; ++n)
   {
      f->next = &(bwb_prefuncs[n]);
      f = f->next;
      FunctionDefinitionCheck(f);
   }

   /* link the last pointer to the end; this completes the list */

   f->next = &LOCALTASK fnc_end;

   return TRUE;
}





void
fnc_add_deffn(struct fslte * F)
{
   struct bwb_function *f;
   struct bwb_function *n;

   bwx_DEBUG(__FUNCTION__);

   /* get memory for fslt structure */

   if ((f = CALLOC(1, sizeof(struct bwb_function), "fnc_add_deffn")) == NULL)
   {
      bwb_error("in fnc_add_deffn(): failed to get memory for bwb_function structure");
      return;
   }
   strcpy(f->Name, F->name);
   if (strchr(F->name, BasicStringSuffix) != NULL)
   {
      f->ReturnType = STRING;
   }
   else
   {
      f->ReturnType = NUMBER;
   }
   f->ReturnType |= (F->line->cmdnum & 0x00FF) << 8;  /* UniqueID is a line
                         * number */
   f->UniqueID = F->line->number;
   f->ParameterCount = F->ParameterCount; /* 0..32, 0xFF == VARIANT */
   f->ParameterTypes = F->ParameterTypes;
   f->OptionVersionBitmask = OptionVersion;

   /* establish linkages */

   n = CURTASK fnc_start.next;
   CURTASK         fnc_start.next = f;
   f->next = n;

}




static struct fslte *
fslt_find_by_line(int LineNumber)
{
   struct fslte   *f;

   bwx_DEBUG(__FUNCTION__);

   /* SEARCH */

   for (f = CURTASK fslt_start.next; f != &CURTASK fslt_end; f = f->next)
   {
      if (f->line->number == LineNumber)
      {
         /* FOUND */
         return f;
      }
   }

   /* NOT FOUND */

   return NULL;

}



/* 
The generic handler for user defined functions.  
When called by exp_function(), f->id will be set to the line number of a specific DEF USR.
*/
struct bwb_variable *
fnc_deffn(int argc, struct bwb_variable * argv, int unique_id)
{
   struct fslte   *f;
   struct bwb_variable *v;
   struct bwb_variable *argn;
   int             i;
   struct bwb_line *call_line;
   int             save_elevel;

   bwx_DEBUG(__FUNCTION__);

   /* initialize the variable if necessary */

   f = fslt_find_by_line(unique_id);

   /* these errors should not occur */
   if (f == NULL)
   {
      sprintf(bwb_ebuf, "INTERNAL ERROR, f == NULL");
      bwb_error(bwb_ebuf);
      return NULL;
   }
   if (argv == NULL)
   {
      sprintf(bwb_ebuf, "INTERNAL ERROR, argv == NULL");
      bwb_error(bwb_ebuf);
      return NULL;
   }
   if (f->ParameterCount == 0xFF)
   {
      /* VARIANT */
   }
   else
   if (argc != f->ParameterCount)
   {
      sprintf(bwb_ebuf, "INTERNAL ERROR, argc != f->ParameterCount");
      bwb_error(bwb_ebuf);
      return NULL;
   }
   if (f->ParameterCount == 0xFF)
   {
      /* VARIANT */
      f->local_variable = argv;
   }
   else
   if (argc > 0)
   {
      v = f->local_variable;
      argn = argv;
      for (i = 0; i < argc; i++)
      {
         argn = argn->next;
         if (v == NULL)
         {
            sprintf(bwb_ebuf, "INTERNAL ERROR, v == NULL");
            bwb_error(bwb_ebuf);
            return NULL;
         }
         if (argn == NULL)
         {
            sprintf(bwb_ebuf, "INTERNAL ERROR, argn == NULL");
            bwb_error(bwb_ebuf);
            return NULL;
         }
         if (v->type != argn->type)
         {
            sprintf(bwb_ebuf, "INTERNAL ERROR, v->type != argn->type");
            bwb_error(bwb_ebuf);
            return NULL;
         }
         /* NEW ... */
         if (strlen(v->name) > 0)
         {
            /* FIXME: this is a hack - the variable type
             * should be determined when the variable is
             * created */
            char           *E;
            char            e;
            int             IsError;
            IsError = 0;

            E = strchr(v->name, '\0');
            E--;
            e = *E;
            /* sprintf( bwb_ebuf, "LAST CHAR is %c, TYPE
             * is %c", e, v->type ); puts( bwb_ebuf ); */
            /* so the following code is easier to read
             * and maintain */
#define PARAM_NUMBER   *argn->memnum
#define PARAM_LENGTH    argn->memstr->length
#define PARAM_BUFFER    argn->memstr->sbuffer
            /* check actual values versus formal
             * parameter type */
            switch (e)
            {
            case BasicStringSuffix:
               IsError = StringLengthCheck(P1ANY, PARAM_LENGTH);
               break;
            case BasicDoubleSuffix:
               IsError = NumberValueCheck(P1DBL, PARAM_NUMBER);
               break;
            case BasicSingleSuffix:
               IsError = NumberValueCheck(P1FLT, PARAM_NUMBER);
               break;
            case BasicCurrencySuffix:
               IsError = NumberValueCheck(P1CUR, PARAM_NUMBER);
               break;
            case BasicLongSuffix:
               IsError = NumberValueCheck(P1LNG, PARAM_NUMBER);
               break;
            case BasicIntegerSuffix:
               IsError = NumberValueCheck(P1INT, PARAM_NUMBER);
               break;
            }
            if (IsError != 0)
            {
               sprintf(bwb_ebuf, "ILLEGAL FUUNCTION CALL: %s(%s)", f->name, v->name);
               bwb_Warning_InvalidParameter(bwb_ebuf);
               return argv;
            }
         }
         /* ...NEW */
         v = v->next;
      }
   }
   /* OK */
   call_line = f->line; /* line to call for function */
   call_line->position = f->startpos;

   if (call_line->cmdnum == C_DEF)
   {
      adv_ws(call_line->buffer, &call_line->position);
      if (call_line->buffer[call_line->position] != '=')
      {
         sprintf(bwb_ebuf, "*** INTERNAL ERROR #%d (%s) ***", 5, "call_line->buffer[ ] != '='");
         bwb_error(bwb_ebuf);
         return argv;
      }
      call_line->position++;  /* NOTE - we skip past the '=' sign */
   }
/* PUSH STACK */
   save_elevel = CURTASK exsc;
   bwb_incexec();
   bwb_setexec(call_line, call_line->position, EXEC_FUNCTION);


   /* create variable chain */
   if (f->ParameterCount == 0xFF)
   {
      /* VARIANT */
   }
   else
   if (argc > 0)
   {
      struct bwb_variable *source = NULL; /* source variable */
      source = f->local_variable;
      argn = argv;
      for (i = 0; i < argc; i++)
      {
         argn = argn->next;
         /* copy the name */
         strcpy(argn->name, source->name);
         source = source->next;
      }
   }
   if (call_line->cmdnum == C_DEF)
   {
      struct exp_ese *e;

      /* the function return variable is hidden */
      CURTASK         excs[CURTASK exsc].local_variable = argv->next;
      e = bwb_exp(call_line->buffer, FALSE, &call_line->position);
      bwb_etov(argv, e);

      /* break variable chain */
      CURTASK         excs[CURTASK exsc].local_variable = NULL;


/* POP STACK */
      bwb_decexec();

   }
   else
   {
      /* the function return variable is visible */
      CURTASK         excs[CURTASK exsc].local_variable = argv;
      /* exp_isfn() uses these to determine use of the function
       * name as a variable */
      CURTASK         excs[CURTASK exsc].LoopTopLine = call_line;
      CURTASK         excs[CURTASK exsc].LoopBottomLine = call_line->OtherLine;

      /* execute until function returns */
      while (CURTASK exsc > save_elevel)
      {
         bwb_execline();
      }

   }

   if (f->ParameterCount == 0xFF)
   {
      /* VARIANT */
      f->local_variable = NULL;
   }
   return argv;
}







/***************************************************************
  
        FUNCTION:       fnc_find()
  
        DESCRIPTION:    This C function attempts to locate
                        a BASIC function with the specified name.
                        If successful, it returns a pointer to
                        the C structure for the BASIC function,
                        if not successful, it returns NULL.
  
***************************************************************/

struct bwb_function *
fnc_find(char *name)
{
   struct bwb_function *f;

   bwx_DEBUG(__FUNCTION__);

   if (strlen(name) == 0)
   {
      return NULL;
   }
   for (f = CURTASK fnc_start.next; f != &CURTASK fnc_end; f = f->next)
   {
      if (OptionVersion & f->OptionVersionBitmask)
         if (strcasecmp(f->Name, name) == 0)
         {
            return f;
         }
   }

   /* search has failed: return NULL */

   return NULL;

}


struct bwb_function *
fnc_find_exact(char *name, int ParameterCount, int ParameterTypes)
{
   struct bwb_function *f;

   bwx_DEBUG(__FUNCTION__);

   for (f = CURTASK fnc_start.next; f != &CURTASK fnc_end; f = f->next)
   {
      if (OptionVersion & f->OptionVersionBitmask)
      {
         if (f->ParameterCount == ParameterCount)
         {
            if (f->ParameterTypes == ParameterTypes)
            {
               if (strcasecmp(f->Name, name) == 0)
               {
                  return f;
               }
            }
         }
      }
   }
   /* search has failed: return NULL */
   return NULL;
}



struct bwb_variable *
var_free(struct bwb_variable * v)
{
   /* // Release all the memory associated with a specific variable. //
    * This function returns NULL, so you can use it like this: //     v
    * = var_new(...); //     ... //     v = var_free( v ); */
   bwx_DEBUG(__FUNCTION__);

   if (v != NULL)
   {
      if (v->next != NULL)
      {
         /* This allows variable chains to be easily released. */
         v->next = var_free(v->next);
      }
      /* cleanup this variable */
      if (v->memnum != NULL)
      {
         FREE(v->memnum, "var_free");
         v->memnum = NULL;
      }
      if (v->memstr != NULL)
      {
         int             j;
         for (j = 0; j < v->array_units; ++j)
         {
            if (v->memstr[j].sbuffer != NULL)
            {
               FREE(v->memstr[j].sbuffer, "var_free");
               v->memstr[j].sbuffer = NULL;
            }
            v->memstr[j].rab = FALSE;
            v->memstr[j].length = 0;
         }
         FREE(v->memstr, "var_free");
         v->memstr = NULL;
      }
      if (v->array_sizes != NULL)
      {
         FREE(v->array_sizes, "var_free");
         v->array_sizes = NULL;
      }
      if (v->array_pos != NULL)
      {
         FREE(v->array_pos, "var_free");
         v->array_pos = NULL;
      }
   }
   return NULL;
}

struct bwb_function *
fnc_find_by_id(int unique_id)
{
   /* NOTE: This should ONLY search the INTRINSIC functions, not USER
    * functions  */
   int             i;

   bwx_DEBUG(__FUNCTION__);

   for (i = 0; i < NUM_FUNCTIONS; i++)
   {
      if (bwb_prefuncs[i].UniqueID == unique_id)
      {
         /* FOUND */
         return &(bwb_prefuncs[i]);
      }
   }
   /* NOT FOUND */
   return NULL;
}

struct bwb_line *
bwb_CHANGE(struct bwb_line * l)
{
   /* * CHANGE A$ TO X * CHANGE X TO A$ */

   char            tbuf[BasicStringLengthMax + 1];
   struct bwb_variable *v = NULL;
   struct bwb_variable *A = NULL;
   struct bwb_variable *X = NULL;
   int             IsStringToArray = FALSE;


   bwx_DEBUG(__FUNCTION__);

   if (OptionFlags & OPTION_BASE_ONE)
   {
      bwb_error("CHANGE is only valid for OPTION BASE 0");
      return bwb_zline(l);
   }
   /* get 1st variable */
   adv_ws(l->buffer, &(l->position));
   bwb_getvarname(l->buffer, tbuf, &(l->position));
   v = var_find(tbuf);
   if (v == NULL)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   if (v->type == STRING)
   {
      A = v;
      IsStringToArray = TRUE;
   }
   else
   {
      /* variable MUST be an array */
      if ((v->dimensions == 1) && (v->array_sizes[0] == 1))
      {
         bwb_error(err_syntax);
         return bwb_zline(l);
      }
      X = v;
      IsStringToArray = FALSE;
   }
   /* get "TO" */
   adv_ws(l->buffer, &(l->position));
   adv_element(l->buffer, &(l->position), tbuf);
   if (strcasecmp(tbuf, "TO") != 0)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   /* get 2nd variable */
   adv_ws(l->buffer, &(l->position));
   bwb_getvarname(l->buffer, tbuf, &(l->position));
   v = var_find(tbuf);
   if (v == NULL)
   {
      bwb_error(err_syntax);
      return bwb_zline(l);
   }
   if (v->type == STRING)
   {
      A = v;
      if (IsStringToArray == TRUE)
      {
         bwb_error(err_syntax);
         return bwb_zline(l);
      }
   }
   else
   {
      /* variable MUST be an array */
      if ((v->dimensions == 1) && (v->array_sizes[0] == 1))
      {
         bwb_error(err_syntax);
         return bwb_zline(l);
      }
      X = v;
      if (IsStringToArray == FALSE)
      {
         bwb_error(err_syntax);
         return bwb_zline(l);
      }
   }
   if (IsStringToArray)
   {
      /* CHANGE A$ TO X */
      int             i;
      int             n;
      char           *a;
      BasicNumberType *x;
      unsigned long   t;

      if (A->memstr == NULL)
      {
         bwb_error("String Variable not allocated");
         return bwb_zline(l);
      }
      if (A->memstr->sbuffer == NULL)
      {
         bwb_error("String Variable not allocated");
         return bwb_zline(l);
      }
      /* variable storage is a mess, we bypass that tradition here. */
      t = 1;
      for (n = 0; n < X->dimensions; n++)
      {
         t *= X->array_sizes[n];
      }
      if (t <= A->memstr->length)
      {
         bwb_error("Numeric Array too small");
         return bwb_zline(l);
      }
      n = A->memstr->length;
      a = A->memstr->sbuffer;
      x = X->memnum;
      *x = n;
      x++;
      for (i = 0; i < n; i++)
      {
         char            C;
         BasicNumberType V;
         C = *a;
         V = C;
         *x = V;
         x++;
         a++;
      }
   }
   else
   {
      /* CHANGE X TO A$ */
      int             i;
      int             n;
      char           *a;
      BasicNumberType *x;
      unsigned long   t;

      if (A->memstr == NULL)
      {
         bwb_error("String Variable not allocated");
         return bwb_zline(l);
      }
      if (A->memstr->sbuffer == NULL)
      {
         bwb_error("String Variable not allocated");
         return bwb_zline(l);
      }
      /* variable storage is a mess, we bypass that tradition here. */
      t = 1;
      for (n = 0; n < X->dimensions; n++)
      {
         t *= X->array_sizes[n];
      }
      if (t <= 1)
      {
         bwb_error("Numeric Array to small");
         return bwb_zline(l);
      }
      if (t > BasicStringLengthMax)
      {
         bwb_error("Numeric Array too large");
         return bwb_zline(l);
      }
      /* n = A->memstr->length; */
      a = A->memstr->sbuffer;
      x = X->memnum;
      n = *x;
      x++;
      for (i = 0; i < n; i++)
      {
         char            C;
         BasicNumberType V;
         V = *x;
         C = V;
         *a = C;
         x++;
         a++;
      }
   }
   return bwb_zline(l);
}

/* EOF */
