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

        bwb_stc.c       Commands Related to Structured Programming
                        for Bywater BASIC Interpreter

                        Commands:	CALL
					SUB
					FUNCTION
					END SUB
					END FUNCTION

                        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).                                  */
/*---------------------------------------------------------------*/

#include <stdio.h>
#include <ctype.h>

#include "bwbasic.h"
#include "bwb_mes.h"

/* prototypes */

#if ANSI_C
static int fslt_clear( void );
static int fslt_add( struct bwb_line *line, int *position, int code );
static struct bwb_line *fslt_findl( char *buffer );
static struct fslte    *fslt_findf( char *buffer );
static int scan_getcmd( struct bwb_line *line, int *position );
static int scan_readargs( struct fslte *f,
   struct bwb_line *line, int *position );
static int call_readargs( struct fslte *f,
   char *expression, int *position );
static int is_endsub( struct bwb_line *l );
static struct bwb_line *find_endsub( struct bwb_line *l );
static struct bwb_line *bwb_loopuntil( struct bwb_line *l );
struct bwb_variable *bwb_vtov( struct bwb_variable *dst, struct bwb_variable *src );
struct bwb_variable *bwb_etov( struct bwb_variable *dst, struct exp_ese *src );
struct bwb_variable *var_pos( struct bwb_variable *firstvar, int p );
int fslt_addcallvar( struct bwb_variable *v );
int fslt_addlocalvar( struct fslte *f, struct bwb_variable *v );
#else
static int fslt_clear();
static int fslt_add();
static struct bwb_line *fslt_findl();
static struct fslte    *fslt_findf();
static int scan_getcmd();
static int scan_readargs();
static int call_readargs();
static int is_endsub();
static struct bwb_line *find_endsub();
static struct bwb_line *bwb_loopuntil();
struct bwb_variable *bwb_vtov();
struct bwb_variable *bwb_etov();
struct bwb_variable *var_pos();
int fslt_addcallvar();
int fslt_addlocalvar();
#endif				/* ANSI_C for prototypes */

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

        FUNCTION:       bwb_scan()

        DESCRIPTION:	This function scans all lines of the
        		program in memory and creates a FUNCTION-
        		SUB lookup table (fslt) for the program.

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

#if ANSI_C
int
bwb_scan( void )
#else
int
bwb_scan()
#endif
   {
   struct bwb_line *current;
   int position;
   int c;

#if PROG_ERRORS
   if ( CURTASK rescan != TRUE )
      {
      bwb_error( "in bwb_scan(): call to scan while CURTASK rescan != TRUE" );
      return FALSE;
      }
#endif

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_scan(): beginning scan..." );
   bwb_debug( bwb_ebuf );
#endif

   /* first run through the FUNCTION - SUB loopkup table
      and free any existing memory */

   fslt_clear();

   /* run through the list of lines and identify SUB and FUNCTION statements */

   for ( current = CURTASK bwb_start.next; current != &CURTASK bwb_end; current = current->next )
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_scan(): scanning line <%d>", current->number );
      bwb_debug( bwb_ebuf );
#endif

      c = scan_getcmd( current, &position );
      if ( c == getcmdnum( CMD_SUB ))
         {
         fslt_add( current, &position, EXEC_CALLSUB );
         }
      else if ( c == getcmdnum( CMD_FUNCTION ))
         {
         fslt_add( current, &position, EXEC_FUNCTION );
         }
      else if ( c == getcmdnum( CMD_DEF ))
         {
         fslt_add( current, &position, EXEC_FUNCTION );
         }
#if STRUCT_CMDS
      else if ( c == getcmdnum( CMD_LABEL ))
         {
         fslt_add( current, &position, EXEC_LABEL );
         }
#endif
      }

   /* return */

   CURTASK rescan = FALSE;
   return TRUE;

   }

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

        FUNCTION:       fslt_clear()

        DESCRIPTION:	This C function clears all existing memory
        		in the FUNCTION-SUB lookup table.

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

#if ANSI_C
static int
fslt_clear( void )
#else
static int
fslt_clear()
#endif
   {
   struct fslte *current, *next;
   struct bwb_variable *c, *n;

   /* run through table and clear memory */

   next = CURTASK fslt_start.next;
   for ( current = CURTASK fslt_start.next; current != &CURTASK fslt_end;
      current = next )
      {

      /* check for local variables and free them */

      c = current->local_variable;
      while ( c != NULL )
         {
         n = c->next;

         /* Revised to FREE pass-thru call by JBV */
         FREE( c, "fslt_clear" );
         c = n;
         }

      next = current->next;

      /* Revised to FREE pass-thru calls by JBV */
      if (current->name != NULL)
      {
          FREE( current->name, "fslt_clear" ); /* JBV */
          current->name = NULL; /* JBV */
      }
      FREE( current, "fslt_clear" );
      current = NULL; /* JBV */
      }

   /* reset linkage */

   CURTASK fslt_start.next = &CURTASK fslt_end;

   return TRUE;
   }

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

        FUNCTION:       scan_getcmd()

        DESCRIPTION:	This command returns the command number
        		for the first BASIC command word encountered
        		in a line.

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

#if ANSI_C
static int
scan_getcmd( struct bwb_line *line, int *position )
#else
static int
scan_getcmd( line, position )
   struct bwb_line *line;
   int *position;
#endif
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];

   *position = 0;
   adv_ws( line->buffer, position );

   /* check for NULL line */

   if ( line->buffer[ *position ] == '\0' )
      {
      return -1;
      }

   /* check for line number and advance beyond it */

   if ( isdigit( line->buffer[ *position ] ))
      {
      scan_element( line->buffer, position, tbuf );
      }

   /* get the command element in the buffer */

   scan_element( line->buffer, position, tbuf );

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in scan_getcmd(): scanning element <%s>", tbuf );
   bwb_debug( bwb_ebuf );
#endif

#if STRUCT_CMDS

   if ( is_label( tbuf ) == TRUE )
      {
#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in scan_getcmd(): found label <%s>", tbuf );
      bwb_debug( bwb_ebuf );
#endif

      return getcmdnum( CMD_LABEL );
      }

#endif

   bwb_strtoupper( tbuf );

   /* return command number */

   return getcmdnum( tbuf );

   }

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

	FUNCTION:       scan_element()

        DESCRIPTION:    This function reads characters in <buffer>
                        beginning at <pos> and advances past a
                        line element, incrementing <pos> appropri-
                        ately and returning the line element in
			<element>.

			This function is almost identical to adv_element(),
			but it will not stop at a full colon.  This is
			necessary to detect a label in the first element
			position.  If MULTISEG_LINES is defined as TRUE,
			adv_element() will stop at the colon, interpreting
			it as the end-of-segment marker.

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

#if ANSI_C
extern int
scan_element( char *buffer, int *pos, char *element )
#else
int
scan_element( buffer, pos, element )
   char *buffer;
   int *pos;
   char *element;
#endif
   {
   int loop;                                    /* control loop */
   int e_pos;                                   /* position in element buffer */
   int str_const;                               /* boolean: building a string constant */

   /* advance beyond any initial whitespace */

   adv_ws( buffer, pos );

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in adv_element(): receieved <%s>.", &( buffer[ *pos ] ));
   bwb_debug( bwb_ebuf );
#endif

   /* now loop while building an element and looking for an
      element terminator */

   loop = TRUE;
   e_pos = 0;
   element[ e_pos ] = '\0';
   str_const = FALSE;

   while ( loop == TRUE )
      {
      switch( buffer[ *pos ] )
         {
         case ',':                      /* element terminators */
         case ';':
	 case '=':
	 case ' ':
         case '\t':
         case '\0':
         case '\n':
         case '\r':
            if ( str_const == TRUE )
               {
               element[ e_pos ] = buffer[ *pos ];
               ++e_pos;
               ++( *pos );
               element[ e_pos ] = '\0';
               }
            else
               {
               return TRUE;
               }
            break;

         case '\"':                     /* string constant */
            element[ e_pos ] = buffer[ *pos ];
            ++e_pos;
            ++( *pos );
            element[ e_pos ] = '\0';
            if ( str_const == TRUE )    /* termination of string constant */
               {
               return TRUE;
               }
            else                        /* beginning of string constant */
               {
               str_const = TRUE;
               }
            break;

         case '(':                     /* MID$ command termination (JBV) */
            /* If MID$ is here, get out */
            if (strcmp(element, CMD_MID) == 0)
               return TRUE;

            /* else add it to the accumulation element */
            element[ e_pos ] = buffer[ *pos ];
            ++e_pos;
            ++( *pos );
            element[ e_pos ] = '\0';
            break;

         default:
            element[ e_pos ] = buffer[ *pos ];
            ++e_pos;
            ++( *pos );
            element[ e_pos ] = '\0';
            break;
         }
      }

   /* This should not happen */

   return FALSE;

   }

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

        FUNCTION:       fslt_add()

        DESCRIPTION:	This C function adds an entry to the
        		FUNCTION-SUB lookup table.

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

#if ANSI_C
static int
fslt_add( struct bwb_line *line, int *position, int code )
#else
static int
fslt_add( line, position, code )
   struct bwb_line *line;
   int *position;
   int code;
#endif
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];
   char *name;
   struct bwb_variable *v;
   struct fslte *f, *n;
   int p;

   /* get the element for name */

   if ( code == EXEC_LABEL )
      {
      p = 0;
      scan_element( line->buffer, &p, tbuf );
      if ( isdigit( tbuf[ 0 ] ))
         {
	 scan_element( line->buffer, &p, tbuf );
         }
      tbuf[ strlen( tbuf ) - 1 ] = '\0';
      }
   else
      {
      adv_ws( line->buffer, position );
      exp_getvfname( &( line->buffer[ *position ] ), tbuf );
      *position += strlen( tbuf );
      }

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in fslt_add(): adding SUB/FUNCTION/LABEL code <%d> name <%s>",
      code, tbuf );
   bwb_debug( bwb_ebuf );
#endif

   /* get memory for name buffer */

   /* Revised to CALLOC pass-thru call by JBV */
   if ( ( name = CALLOC( 1, strlen( tbuf ) + 1, "fslt_add" ) ) == NULL )
      {
#if PROG_ERRORS
      bwb_error( "in fslt_add(): failed to get memory for name buffer" );
#else
      bwb_error( err_getmem );
#endif
      return FALSE;
      }

   strcpy( name, tbuf );

   /* get memory for fslt structure */

   if ( ( f = CALLOC( 1, sizeof( struct fslte ), "fslt_add" ) ) == NULL )
      {
#if PROG_ERRORS
      bwb_error( "in fslt_add(): failed to get memory for fslt structure" );
#else
      bwb_error( err_getmem );
#endif
      return FALSE;
      }

   /* fill in structure */

   f->line = line;
   f->name = name;
   f->code = code;
   f->local_variable = NULL;

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in fslt_add(): current buffer <%s>",
      &( line->buffer[ *position ] ) );
   bwb_debug( bwb_ebuf );
#endif

   /* read arguments */

   adv_ws( line->buffer, position );
   if ( line->buffer[ *position ] == '(' )
      {
      scan_readargs( f, line, position );
      }

   /* if function, add one more local variable expressing the name
      of the function */

   if ( code == EXEC_FUNCTION )
      {

      v = var_new( tbuf );
      fslt_addlocalvar( f, v );

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in fslt_add(): added function-name variable <%s>",
         v->name );
      bwb_debug( bwb_ebuf );
      getchar();
#endif

      }

   /* establish linkages */

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

   return TRUE;

   }

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

        FUNCTION:       scan_readargs()

        DESCRIPTION:	This C function reads arguments (variable
        		names for an entry added to the FUNCTION-
			SUB lookup table.

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

#if ANSI_C
static int
scan_readargs( struct fslte *f, struct bwb_line *line, int *position )
#else
static int
scan_readargs( f, line, position )
   struct fslte *f;
   struct bwb_line *line;
   int *position;
#endif
   {
   int control_loop;
   struct bwb_variable *v;
   char tbuf[ MAXSTRINGSIZE + 1 ];

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in scan_readargs(): reading arguments, buffer <%s>",
      &( line->buffer[ *position ] ) );
   bwb_debug( bwb_ebuf );
#endif

   /* if we are at begin paren, advance */

   if ( line->buffer[ *position ] == '(' )
      {
      ++( *position );
      }

   /* loop through looking for arguments */

   control_loop = TRUE;
   adv_ws( line->buffer, position );
   while ( control_loop == TRUE )
      {

      switch( line->buffer[ *position ] )
         {
         case '\n':		/* premature end of line */
         case '\r':
         case '\0':
            control_loop = FALSE;
            f->startpos = *position;
            bwb_error( err_syntax );
            return FALSE;
         case ')':		/* end of argument list */
            ++( *position );
            control_loop = FALSE;
            f->startpos = *position;
            return TRUE;

         default:		/* presume beginning of argument == variable name */

            exp_getvfname( &( line->buffer[ *position ] ), tbuf );
            *position += strlen( tbuf );

#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in scan_readargs(): read argument <%s>",
               tbuf );
            bwb_debug( bwb_ebuf );
#endif

            /* initialize the variable and add it to local chain */

            v = var_new( tbuf );
            fslt_addlocalvar( f, v );

            /* advance past the comma */

            if ( line->buffer[ *position ] == ',' )
               {
               ++( *position );
               }

            break;
         }

      adv_ws( line->buffer, position );
      }

   return TRUE;

   }

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

        FUNCTION:       call_readargs()

        DESCRIPTION:	This C function reads arguments (variable
        		names for a subroutine CALL or function
			call.

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

#if ANSI_C
static int
call_readargs( struct fslte *f, char *expression, int *position )
#else
static int
call_readargs( f, expression, position )
   struct fslte *f;
   char *expression;
   int *position;
#endif
   {
   int control_loop;
   struct bwb_variable *v, *c;
   char tbuf[ MAXSTRINGSIZE + 1 ];
   int argument_counter;
   int local_pos, single_var;
   struct exp_ese *e;

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in call_readargs(): reading arguments, buffer <%s>",
      &( expression[ *position ] ) );
   bwb_debug( bwb_ebuf );
#endif

   /* if we are at begin paren, advance */

   if ( expression[ *position ] == '(' )
      {
      ++( *position );
      }

   /* loop through looking for arguments */

   control_loop = TRUE;
   argument_counter = 0;

   while ( control_loop == TRUE )
      {

      adv_ws( expression, position );

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in call_readargs(): in loop, buffer <%s>",
         &( expression[ *position ] ) );
      bwb_debug( bwb_ebuf );
#endif

      switch( expression[ *position ] )
         {
         case '\n':		/* end of line */
         case '\r':
	 case '\0':
#if MULTISEG_LINES
	 case ':':              /* end of segment */
#endif
            control_loop = FALSE;
            return FALSE;

         case ')':		/* end of argument list */
            ++( *position );
            control_loop = FALSE;
            return TRUE;

         default:		/* presume beginning of argument */

            /* read the first word to see if it is a single variable name */

            single_var = FALSE;
            exp_getvfname( &( expression[ *position ] ), tbuf );
            local_pos = *position + strlen( tbuf );

            adv_ws( expression, &local_pos );

#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in call_readargs(): in loop, tbuf <%s>",
               tbuf );
            bwb_debug( bwb_ebuf );
#endif

            /* check now for the single variable name */

            if ( strlen( tbuf ) == 0 )
               {
               single_var = FALSE;
               }

            else
               {
               switch ( expression[ local_pos ] )
                  {
                  case ')':		/* end of argument list */

#if INTENSIVE_DEBUG
		     sprintf( bwb_ebuf, "in call_readargs(): detected end of argument list" );
		     bwb_debug( bwb_ebuf );
#endif

		     ++local_pos;       /* and fall through */
                  case '\n':		/* end of line */
                  case '\r':
		  case '\0':
#if MULTISEG_LINES
		  case ':':             /* end of segment */
#endif
		     control_loop = FALSE;      /* and fall through */
						/* added 1993-06-16 */
		  case ',':             /* end of argument */

		     single_var = TRUE;

		     /* look for variable from previous (calling) level */

		     -- CURTASK exsc;
		     v = var_find( tbuf );      /* find variable there */
		     ++ CURTASK exsc;

		     c = var_pos( CURTASK excs[ CURTASK exsc ].local_variable,
                        argument_counter );	/* find local equivalent */
                     bwb_vtov( c, v );		/* assign calling value to local variable */

#if INTENSIVE_DEBUG
		     sprintf( bwb_ebuf, "in call_readargs(): variable name is <%s>, local name <%s>",
			v->name, c->name );
		     bwb_debug( bwb_ebuf );
#endif

		     *position = local_pos;
                     break;
                  default:
                     single_var = FALSE;
                     break;
                  }
               }

            if ( single_var == FALSE )
               {
#if INTENSIVE_DEBUG
               sprintf( bwb_ebuf, "in call_readargs(): in loop, parse expression <%s>",
                  &( expression[ *position ] ) );
               bwb_debug( bwb_ebuf );
#endif

               e = bwb_exp( expression, FALSE, position );	/* parse */

#if INTENSIVE_DEBUG
	       sprintf( bwb_ebuf, "in call_readargs(): in loop, parsed expression, buffer <%s>",
		  &( expression[ *position ] ) );
               bwb_debug( bwb_ebuf );
#endif

               v = var_pos( CURTASK excs[ CURTASK exsc ].local_variable,
                  argument_counter );		/* assign to variable */
               bwb_etov( v, e );		/* assign value */
               }

            /* add the variable to the calling variable chain */

            fslt_addcallvar( v );

#if INTENSIVE_DEBUG
            str_btoc( tbuf, var_getsval( v ));
            if ( single_var == TRUE )
               {
               sprintf( bwb_ebuf, "in call_readargs(): added arg <%d> (single) name <%s> value <%s>",
                  argument_counter, v->name, tbuf );
               }
            else
               {
               sprintf( bwb_ebuf, "in call_readargs(): added arg <%d> (expression) name <%s> value <%s>",
                  argument_counter, v->name, tbuf );
               }
            bwb_debug( bwb_ebuf );
            getchar();
#endif

            /* advance past comma if present */

            adv_ws( expression, position );
            if ( expression[ *position ] == ',' )
               {
               ++( *position );
               }

            break;
         }

      ++argument_counter;

      }

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in call_readargs(): exiting function" );
   bwb_debug( bwb_ebuf );
#endif

   return TRUE;

   }

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

        FUNCTION:       fslt_findl()

        DESCRIPTION:	This C function finds a line corresponding
        		to a name in the FUNCTION-SUB lookup
			table.

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

#if ANSI_C
static struct bwb_line *
fslt_findl( char *buffer )
#else
static struct bwb_line *
fslt_findl( buffer )
   char *buffer;
#endif
   {
   struct fslte *r;

   r = fslt_findf( buffer );

   return r->line;

   }

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

        FUNCTION:       fslt_findf()

        DESCRIPTION:	This C function finds an fslte structure
			corresponding to a name in the FUNCTION-
			SUB lookup table.

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

#if ANSI_C
static struct fslte *
fslt_findf( char *buffer )
#else
static struct fslte *
fslt_findf( buffer )
   char *buffer;
#endif
   {
   struct fslte *f;
   register int c;

   /* remove open-paren from string */

   for ( c = 0; buffer[ c ] != '\0'; ++c )
      {
      if ( buffer[ c ] == '(' )
         {
         buffer[ c ] = '\0';
         }
      }

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in fslt_findf(): search for name <%s>", buffer );
   bwb_debug( bwb_ebuf );
#endif

   /* run through the table */

   for ( f = CURTASK fslt_start.next; f != &CURTASK fslt_end; f = f->next )
      {
      if ( strcmp( f->name, buffer ) == 0 )
         {
         return f;
         }
      }

   /* search has failed */

#if PROG_ERRORS
   sprintf( bwb_ebuf, "in fslt_findf(): failed to find Function/Subroutine <%s>",
      buffer );
   bwb_error( bwb_ebuf );
#else
   bwb_error( err_lnnotfound );
#endif

   return NULL;

   }

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

        FUNCTION:       bwb_def()

        DESCRIPTION:    This C function implements the BASIC
                        DEF statement. Since DEF and FUNCTION
			are equivalent, it simply passes execution
			to bwb_function().

	SYNTAX:		DEF FNname(arg...)] = expression

	NOTE:		It is not a strict requirement that the
			function name should begin with "FN".

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

#if ANSI_C
struct bwb_line *
bwb_def( struct bwb_line *l )
#else
struct bwb_line *
bwb_def( l )
   struct bwb_line *l;
#endif
   {

#if MULTISEG_LINES
   adv_eos( l->buffer, &( l->position ));
#endif

   return bwb_zline( l );
   }

#if STRUCT_CMDS

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

        FUNCTION:       bwb_function()

        DESCRIPTION:	This C function implements the BASIC
			FUNCTION and DEF commands.

	SYNTAX:		FUNCTION function-definition

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

#if ANSI_C
struct bwb_line *
bwb_function( struct bwb_line *l )
#else
struct bwb_line *
bwb_function( l )
   struct bwb_line *l;
#endif
   {

   return bwb_def( l );

   }

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

        FUNCTION:       bwb_endfnc()

        DESCRIPTION:	This C function implements the BASIC
			END FUNCTION command, ending a subroutine
			definition.  Because the command END
			can have multiple meanings, this function
			should be called from the bwb_xend()
			function, which should be able to identify
			an END FUNCTION command.

	SYNTAX:		END FUNCTION

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

#if ANSI_C
struct bwb_line *
bwb_endfnc( struct bwb_line *l )
#else
struct bwb_line *
bwb_endfnc( l )
   struct bwb_line *l;
#endif
   {
   struct bwb_variable *local;
   register int c;

   /* assign local variable values to calling variables */

   local = CURTASK excs[ CURTASK exsc ].local_variable;
   for ( c = 0; c < CURTASK excs[ CURTASK exsc ].n_cvs; ++c )
      {
      bwb_vtov( CURTASK excs[ CURTASK exsc ].calling_variable[ c ], local );
      local = local->next;
      }

   /* decrement the EXEC stack counter */

   bwb_decexec();

   /* and return next from old line */

   CURTASK excs[ CURTASK exsc ].line->next->position = 0;
   return CURTASK excs[ CURTASK exsc ].line->next;

   }

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

        FUNCTION:       bwb_call()

        DESCRIPTION:	This C function implements the BASIC
        		CALL subroutine command.

	SYNTAX:		CALL subroutine-name

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

#if ANSI_C
struct bwb_line *
bwb_call( struct bwb_line *l )
#else
struct bwb_line *
bwb_call( l )
   struct bwb_line *l;
#endif
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];
   struct bwb_line *call_line;
   struct fslte *f;

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

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_call(): call to subroutine <%s>", tbuf );
   bwb_debug( bwb_ebuf );
#endif

   /* find the line to call */

   call_line = fslt_findl( tbuf );
   f = fslt_findf( tbuf );

   if ( call_line == NULL )
      {
      return bwb_zline( l );
      }

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_call(): found line <%s>",
      call_line->buffer );
   bwb_debug( bwb_ebuf );
#endif

   /* save the old position on the EXEC stack */

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

   /* increment and set new EXEC stack */

   bwb_incexec();
   call_line->position = 0;
   bwb_setexec( call_line, 0, EXEC_CALLSUB );

   /* attach local variables */

   CURTASK excs[ CURTASK exsc ].local_variable = f->local_variable;

   /* read calling variables for this call */

   call_readargs( f, l->buffer, &( l->position ) );

   return call_line;

   }

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

        FUNCTION:       bwb_sub()

	DESCRIPTION:    This function implements the BASIC
			SUB command, introducing a named
			subroutine.

	SYNTAX:		SUB subroutine-name
			(followed by subroutine definition ending
			with END SUB).

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

#if ANSI_C
struct bwb_line *
bwb_sub( struct bwb_line *l )
#else
struct bwb_line *
bwb_sub( l )
   struct bwb_line *l;
#endif
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];
   struct bwb_line *rline;
#if MULTISEG_LINES
   struct fslte *f;
#endif

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_sub(): entered function at exec level <%d>",
      CURTASK exsc );
   bwb_debug( bwb_ebuf );
#endif

   /* check current exec level: if 1 then only MAIN should be executed */

   if ( CURTASK exsc == 0 )
      {
      adv_element( l->buffer, &( l->position ), tbuf );
      bwb_strtoupper( tbuf );
      if ( strcmp( tbuf, "MAIN" ) == 0 )
         {
#if INTENSIVE_DEBUG
         sprintf( bwb_ebuf, "in bwb_sub(): found MAIN function at level 0" );
         bwb_debug( bwb_ebuf );
#endif

         bwb_incexec();

         bwb_setexec( l->next, 0, EXEC_MAIN );

         return bwb_zline( l );

         }

      /* if a MAIN function was not found at level 0, then skip the subroutine */

      else
         {

#if INTENSIVE_DEBUG
         sprintf( bwb_ebuf, "in bwb_sub(): found non-MAIN function at level 0" );
         bwb_debug( bwb_ebuf );
#endif

         rline = find_endsub( l );
         bwb_setexec( rline->next, 0, EXEC_CALLSUB );
         rline->next->position = 0;
         return rline->next;
         }
      }

   /* check for integrity of CALL-SUB sequence if above level 0 */

   else if ( CURTASK excs[ CURTASK exsc ].code != EXEC_CALLSUB )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "in bwb_sub(): SUB without CALL" );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_retnogosub );
#endif
      }

   /* advance position */

#if MULTISEG_LINES
   adv_ws( l->buffer, &( l->position ));
   adv_element( l->buffer, &( l->position ), tbuf );
   f = fslt_findf( tbuf );

   l->position = f->startpos;

   return bwb_zline( l );
#else
   return bwb_zline( l );
#endif
   }

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

        FUNCTION:       find_endsub()

        DESCRIPTION:    This function searches for a line containing
                        an END SUB statement corresponding to a previous
                        SUB statement.

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

#if ANSI_C
static struct bwb_line *
find_endsub( struct bwb_line *l )
#else
static struct bwb_line *
find_endsub( l )
   struct bwb_line *l;
#endif
   {
   struct bwb_line *current;
   register int s_level;
   int position;

   s_level = 1;
   for ( current = l->next; current != &CURTASK bwb_end; current = current->next )
      {
      position = 0;
      if ( current->marked != TRUE )
         {
         line_start( current->buffer, &position, &( current->lnpos ),
            &( current->lnum ),
            &( current->cmdpos ),
            &( current->cmdnum ),
            &( current->startpos ) );
         }
      current->position = current->startpos;

      if ( current->cmdnum > -1 )
         {

         if ( bwb_cmdtable[ current->cmdnum ].vector == bwb_sub )
            {
            ++s_level;

#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in find_endsub(): found SUB at line %d, level %d",
               current->number, s_level );
            bwb_debug( bwb_ebuf );
#endif

            }
         else if ( is_endsub( current ) == TRUE )
            {
            --s_level;

#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in find_endsub(): found END SUB at line %d, level %d",
               current->number, s_level );
            bwb_debug( bwb_ebuf );
#endif

            if ( s_level == 0 )
               {
               return current;
               }
            }

         }
      }

#if PROG_ERRORS
   sprintf( bwb_ebuf, "SUB without END SUB" );
   bwb_error( bwb_ebuf );
#else
   bwb_error( err_syntax  );
#endif

   return NULL;

   }

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

	FUNCTION:       is_endsub()

	DESCRIPTION:    This function determines whether the
			line buffer for line 'l' is positioned
			at an END SUB statement.

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

#if ANSI_C
static int
is_endsub( struct bwb_line *l )
#else
static int
is_endsub( l )
   struct bwb_line *l;
#endif
   {
   int position;
   char tbuf[ MAXVARNAMESIZE + 1];

   if ( bwb_cmdtable[ l->cmdnum ].vector != bwb_xend )
      {
      return FALSE;
      }

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

   if ( strcmp( tbuf, "SUB" ) == 0 )
      {
      return TRUE;
      }

   return FALSE;

   }

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

        FUNCTION:       bwb_endsub()

        DESCRIPTION:	This C function implements the BASIC
			END SUB command, ending a subroutine
			definition.  Because the command END
			can have multiple meanings, this function
			should be called from the bwb_xend()
			function, which should be able to identify
			an END SUB command.

	SYNTAX:		END SUB

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

#if ANSI_C
struct bwb_line *
bwb_endsub( struct bwb_line *line )
#else
struct bwb_line *
bwb_endsub( line )
   struct bwb_line *line;
#endif
   {
   struct bwb_variable *l;
   register int c;

   /* assign local variable values to calling variables */

   l = CURTASK excs[ CURTASK exsc ].local_variable;
   for ( c = 0; c < CURTASK excs[ CURTASK exsc ].n_cvs; ++c )
      {
      bwb_vtov( CURTASK excs[ CURTASK exsc ].calling_variable[ c ], l );
      l = l->next;
      }

   /* decrement the EXEC stack counter */

   bwb_decexec();

   /* if the previous level was EXEC_MAIN,
      then execution continues from this point */

   if ( CURTASK excs[ CURTASK exsc + 1 ].code == EXEC_MAIN )
      {
      return bwb_zline( line );
      }

   /* else return next from old line */

   CURTASK excs[ CURTASK exsc ].line->next->position = 0;
   return CURTASK excs[ CURTASK exsc ].line->next;

   }

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

	FUNCTION:	find_label()

	DESCRIPTION:	This C function finds a program line that
			begins with the label included in <buffer>.

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

#if ANSI_C
extern struct bwb_line *
find_label( char *buffer )
#else
extern struct bwb_line *
find_label( buffer )
   char *buffer;
#endif
   {
   struct fslte *f;

   for ( f = CURTASK fslt_start.next; f != & ( CURTASK fslt_end ); f = f->next )
      {
      if ( strcmp( buffer, f->name ) == 0 )
         {
#if INTENSIVE_DEBUG
         sprintf( bwb_ebuf, "in find_label(): found label <%s>", buffer );
         bwb_debug( bwb_ebuf );
#endif
         return f->line;
         }
      }

#if PROG_ERRORS
   sprintf( bwb_ebuf, "in find_label(): failed to find label <%s>", buffer );
   bwb_error( bwb_ebuf );
#else
   bwb_error( err_lnnotfound );
#endif

   return NULL;

   }

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

	FUNCTION:       bwb_doloop()

	DESCRIPTION:    This C function implements the ANSI BASIC
			DO statement, when DO is not followed by
			an argument.  It is called by bwb_do() in
			bwb_cmd.c.

	SYNTAX:		DO

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

#if ANSI_C
struct bwb_line *
bwb_doloop( struct bwb_line *l )
#else
struct bwb_line *
bwb_doloop( l )
   struct bwb_line *l;
#endif
   {

   /* if this is the first time at this DO statement, note it */

   if ( CURTASK excs[ CURTASK exsc ].while_line != l )
      {

      bwb_incexec();
      CURTASK excs[ CURTASK exsc ].while_line = l;

      /* find the LOOP statement */

      CURTASK excs[ CURTASK exsc ].wend_line = find_loop( l );

      if ( CURTASK excs[ CURTASK exsc ].wend_line == NULL )
	 {
	 return bwb_zline( l );
	 }

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_doloop(): initialize DO loop, line <%d>",
	 l->number );
      bwb_debug( bwb_ebuf );
#endif

      }
#if INTENSIVE_DEBUG
   else
      {
      sprintf( bwb_ebuf, "in bwb_doloop(): return to DO loop, line <%d>",
	 l->number );
      bwb_debug( bwb_ebuf );
      }
#endif

   bwb_setexec( l, l->position, EXEC_DO );
   return bwb_zline( l );
   }

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

        FUNCTION:       bwb_loop()

	DESCRIPTION:    This C function implements the ANSI BASIC
			LOOP statement.

	SYNTAX:		LOOP [UNTIL expression]
			
***************************************************************/

#if ANSI_C
struct bwb_line *
bwb_loop( struct bwb_line *l )
#else
struct bwb_line *
bwb_loop( l )
   struct bwb_line *l;
#endif
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_loop(): entered subroutine" );
   bwb_debug( bwb_ebuf );
#endif

   /* If the current exec stack is set for EXEC_WHILE, then we
      presume that this is a LOOP statement ending a DO WHILE
      loop */

   if ( CURTASK excs[ CURTASK exsc ].code == EXEC_WHILE )
      {
      return bwb_wend( l );
      }

   /* check integrity of DO loop */

   if ( CURTASK excs[ CURTASK exsc ].code != EXEC_DO )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "in bwb_loop(): exec stack code != EXEC_DO" );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_syntax );
#endif
      }

   if ( CURTASK excs[ CURTASK exsc ].while_line == NULL )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "in bwb_loop(): exec stack while_line == NULL" );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_syntax );
#endif
      }

   /* advance to find the first argument */

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

   /* detect a LOOP UNTIL structure */

   if ( strcmp( tbuf, CMD_XUNTIL ) == 0 )
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_loop(): detected LOOP UNTIL" );
      bwb_debug( bwb_ebuf );
#endif

      return bwb_loopuntil( l );

      }

   /* LOOP does not have UNTIL */

   else
      {

      /* reset to the top of the current DO loop */

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_loop() return to line <%d>",
	 CURTASK excs[ CURTASK exsc ].while_line->number );
      bwb_debug( bwb_ebuf );
#endif

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

      return CURTASK excs[ CURTASK exsc ].while_line;

      }

   }

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

	FUNCTION:       bwb_loopuntil()

	DESCRIPTION:    This C function implements the ANSI BASIC
			LOOP UNTIL statement and is called by
			bwb_loop().

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

#if ANSI_C
static struct bwb_line *
bwb_loopuntil( struct bwb_line *l )
#else
static struct bwb_line *
bwb_loopuntil( l )
   struct bwb_line *l;
#endif
   {
   struct exp_ese *e;
   struct bwb_line *r;

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_loopuntil(): entered subroutine" );
   bwb_debug( bwb_ebuf );
#endif

   /* call bwb_exp() to interpret the expression */

   e = bwb_exp( l->buffer, FALSE, &( l->position ) );

   if ( (int) exp_getnval( e ) != FALSE ) /* Was == TRUE (JBV 10/1996) */
      {
      CURTASK excs[ CURTASK exsc ].while_line = NULL;
      r = CURTASK excs[ CURTASK exsc ].wend_line;
      bwb_setexec( r, 0, CURTASK excs[ CURTASK exsc - 1 ].code );
      r->position = 0;
      bwb_decexec();
      return r;
      }

   /* condition is false: loop around to DO again */

   else
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_loopuntil() return to line <%d>",
	 CURTASK excs[ CURTASK exsc ].while_line->number );
      bwb_debug( bwb_ebuf );
#endif

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

      return CURTASK excs[ CURTASK exsc ].while_line;

      }

   }

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

        FUNCTION:       bwb_exit()

	DESCRIPTION:    This C function implements the BASIC EXIT
			statement, calling subroutines for either
			EXIT FOR or EXIT DO.

	SYNTAX:		EXIT FOR|DO

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

#if ANSI_C
struct bwb_line *
bwb_exit( struct bwb_line *l )
#else
struct bwb_line *
bwb_exit( l )
   struct bwb_line *l;
#endif
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_exit(): entered subroutine" );
   bwb_debug( bwb_ebuf );
#endif

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

   if ( strcmp( tbuf, CMD_XFOR ) == 0 )
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_exit(): detected EXIT FOR" );
      bwb_debug( bwb_ebuf );
#endif

      return bwb_exitfor( l );
      }

   if ( strcmp( tbuf, CMD_XDO ) == 0 )
      {
      return bwb_exitdo( l );
      }

#if PROG_ERRORS
   sprintf( bwb_ebuf, "in bwb_exit(): Nonsense or nothing following EXIT" );
   bwb_error( bwb_ebuf );
#else
   bwb_error( err_syntax );
#endif

   return bwb_zline( l );
   }

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

	FUNCTION:       bwb_exitdo()

	DESCRIPTION:    This function handles the BASIC EXIT
			DO statement.  This is a structured
			programming command compatible with ANSI
			BASIC. It is called from the bwb_exit()
			subroutine.

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

#if ANSI_C
struct bwb_line *
bwb_exitdo( struct bwb_line *l )
#else
struct bwb_line *
bwb_exitdo( l )
   struct bwb_line *l;
#endif
   {
   struct bwb_line *next_line;
   int found;
   register int level;

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_exitdo(): entered subroutine" );
   bwb_debug( bwb_ebuf );
#endif

   /* Check the integrity of the DO statement */

   found = FALSE;
   level = CURTASK exsc;
   do
      {
      if ( CURTASK excs[ level ].code == EXEC_DO )
	 {
	 next_line = CURTASK excs[ CURTASK level ].wend_line;
	 found = TRUE;
	 }
      else
	 {
	 --level;
	 }
      }
   while ( ( level >= 0 ) && ( found == FALSE ) );

   if ( found != TRUE )
      {

#if PROG_ERRORS
      /* JBV 1/97 (was "bwb_exitfor") */
      sprintf( bwb_ebuf, "in bwb_exitdo(): EXIT DO without DO" );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_syntax );
#endif

      return bwb_zline( l );

      }

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_exitdo(): level found is <%d>, current <%d>",
      level, CURTASK exsc );
   bwb_debug( bwb_ebuf );
#endif

   /* decrement below the level of the NEXT statement */

   while( CURTASK exsc >= level )
      {
      bwb_decexec();
      }

   /* set the next line in the exec stack */

   next_line->position = 0;
   bwb_setexec( next_line, 0, EXEC_NORM );

   return next_line;

   }

#endif					/* STRUCT_CMDS */

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

	FUNCTION:       bwb_vtov()

	DESCRIPTION:    This function assigns the value of one
			bwBASIC variable (src) to the value of another
			bwBASIC variable (dst).

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

#if ANSI_C
struct bwb_variable *
bwb_vtov( struct bwb_variable *dst,
   struct bwb_variable *src )
#else
struct bwb_variable *
bwb_vtov( dst, src )
   struct bwb_variable *dst;
   struct bwb_variable *src;
#endif
   {

   if ( dst == src )
      {
      return dst;
      }

   if ( src->type != dst->type )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "in bwb_vtov(): mismatch src <%s> type <%d> dst <%s> type <%d>",
	 src->name, src->type, dst->name, dst->type );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_mismatch );
#endif
      return NULL;
      }

   if ( dst->type == NUMBER )
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_vtov(): assigning var <%s> val <%lf> to var <%s>",
	 src->name, var_getnval( src ), dst->name );
      bwb_debug( bwb_ebuf );
#endif

      * var_findnval( dst, dst->array_pos ) = var_getnval( src );
      }
   else
      {
      str_btob( var_getsval( dst ), var_getsval( src ) );
      }

   return dst;
   }

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

	FUNCTION:       bwb_etov()

	DESCRIPTION:    This function assigns the value of a
			bwBASIC expression stack element (src)
			to the value of a bwBASIC variable (dst).

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

#if ANSI_C
struct bwb_variable *
bwb_etov( struct bwb_variable *dst, struct exp_ese *src )
#else
struct bwb_variable *
bwb_etov( dst, src )
   struct bwb_variable *dst;
   struct exp_ese *src;
#endif
   {

   if ( (int) src->type != dst->type )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "in bwb_etov(): mismatch src <%d> dst <%d>",
         src->type, dst->type );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_mismatch );
#endif
      return NULL;
      }

   if ( dst->type == NUMBER )
      {
      * var_findnval( dst, dst->array_pos ) = exp_getnval( src );
      }
   else
      {
      str_btob( var_getsval( dst ), exp_getsval( src ) );
      }

   return dst;
   }

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

	FUNCTION:       var_pos()

	DESCRIPTION:    This function returns the name of a
			local variable at a specified position
			in the local variable list.

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

#if ANSI_C
struct bwb_variable *
var_pos( struct bwb_variable *firstvar, int p )
#else
struct bwb_variable *
var_pos( firstvar, p )
   struct bwb_variable *firstvar;
   int p;
#endif
   {
   register int c;
   struct bwb_variable *v;

   v = firstvar;
   for ( c = 0; c != p; ++c )
      {
      v = v->next;
      }

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in var_pos(): returning pos <%d> variable <%s>",
      p, v->name );
   bwb_debug( bwb_ebuf );
#endif

   return v;
   }

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

	FUNCTION:       fslt_addcallvar()

	DESCRIPTION:    This function adds a calling variable
			to the FUNCTION-SUB lookup table at
			a specific level.

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

#if ANSI_C
int
fslt_addcallvar( struct bwb_variable *v )
#else
int
fslt_addcallvar( v )
   struct bwb_variable *v;
#endif
   {

   if ( CURTASK excs[ CURTASK exsc ].n_cvs >= MAX_FARGS )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "in fslt_addcallvar(): Maximum number of Function Args Exceeded" );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_overflow );
#endif
      }

   CURTASK excs[ CURTASK exsc ].calling_variable[ CURTASK excs[ CURTASK exsc ].n_cvs ] = v;
   ++CURTASK excs[ CURTASK exsc ].n_cvs;

   return TRUE;

   }

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

	FUNCTION:       exp_ufnc()

        DESCRIPTION:	This C function interprets a user-defined
			function, returning its value at the current
			level of the expression stack.

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

#if ANSI_C
int
exp_ufnc( char *expression )
#else
int
exp_ufnc( expression )
   char *expression;
#endif
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];
   struct bwb_line *call_line;
   struct fslte *f, *c;
   struct bwb_variable *v, *r;
   struct exp_ese *e;
   int save_elevel;
   int position, epos;
#if INTENSIVE_DEBUG
   register int i;
#endif

   position = 0;

   /* get the function name in tbuf */

   exp_getvfname( expression, tbuf );

   /* find the function name in the function-subroutine lookup table */

   for ( f = CURTASK fslt_start.next; f != &CURTASK fslt_end; f = f->next )
      {
      if ( strcmp( f->name, tbuf ) == 0 )
	 {
#if INTENSIVE_DEBUG
	 sprintf( bwb_ebuf, "in exp_ufnc(): found user function <%s>",
	    tbuf );
	 bwb_debug( bwb_ebuf );
#endif
	 c = f; 		/* current function-subroutine lookup table element */
	 call_line = f->line;	/* line to call for function */
	 }
      }

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in exp_ufnc(): call to function <%s>", tbuf );
   bwb_debug( bwb_ebuf );
#endif

   position += strlen( tbuf );

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in exp_ufnc(): found line <%s>",
      call_line->buffer );
   bwb_debug( bwb_ebuf );
#endif

   /* save the old position on the EXEC stack */

   bwb_setexec( CURTASK excs[ CURTASK exsc ].line,
      position, CURTASK excs[ CURTASK exsc ].code );
   save_elevel = CURTASK exsc;

   /* increment and set new EXEC stack */

   bwb_incexec();
   call_line->position = 0;
   bwb_setexec( call_line, 0, EXEC_FUNCTION );

   /* attach local variables */

   CURTASK excs[ CURTASK exsc ].local_variable = c->local_variable;

#if INTENSIVE_DEBUG
   i = 0;
   sprintf( bwb_ebuf, "in exp_ufnc(): <%s> attached local variables EXEC level <%d>",
      tbuf, CURTASK exsc );
   bwb_debug( bwb_ebuf );
   for ( v = CURTASK excs[ CURTASK exsc ].local_variable; v != NULL; v = v->next )
      {
      sprintf( bwb_ebuf, "in exp_ufnc():   <%s> level <%d> variable <%d> name <%s>",
	 tbuf, CURTASK exsc, i, v->name );
      bwb_debug( bwb_ebuf );
      ++i;
      }
   getchar();
#endif

   /* read calling variables for this call */

   call_readargs( c, expression, &position );

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in exp_ufnc(): current buffer <%s>",
      &( call_line->buffer[ c->startpos ] ) );
   bwb_debug( bwb_ebuf );
#endif

   /* determine if single-line function */

   epos = c->startpos;
   adv_ws( call_line->buffer, &epos );
   if ( call_line->buffer[ epos ] == '=' )
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in exp_ufnc(): found SINGLE-LINE function" );
      bwb_debug( bwb_ebuf );
#endif

      ++epos;
      call_line->position = epos;
      bwb_setexec( call_line, epos, EXEC_FUNCTION );

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in exp_ufnc(): single line: parse <%s>",
	 &( call_line->buffer[ epos ] ) );
      bwb_debug( bwb_ebuf );
#endif

      e = bwb_exp( call_line->buffer, FALSE, &epos );
      v = var_find( tbuf );

#if INTENSIVE_DEBUG
      if ( e->type == STRING )
	 {
	 sprintf( bwb_ebuf, "in exp_ufnc(): expression returns <%d>-byte string",
	    exp_getsval( e )->length );
	 bwb_debug( bwb_ebuf );
	 }
      else
	 {
	 sprintf( bwb_ebuf, "in exp_ufnc(): expression returns number <%lf>",
	    (double) exp_getnval( e ) );
	 bwb_debug( bwb_ebuf );
	 }
#endif

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in exp_ufnc(): single line after parsing, <%s>",
	 &( call_line->buffer[ epos ] ) );
      bwb_debug( bwb_ebuf );
#endif

      bwb_etov( v, e );
      bwb_decexec();
      }

   /* multi-line function must be executed now */

   else
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in exp_ufnc(): found MULTI-LINE function" );
      bwb_debug( bwb_ebuf );
#endif

      /* now execute until function is resolved */

      bwb_execline();
      while( CURTASK exsc > save_elevel )
	 {
	 bwb_execline();
	 }

      /* find the return value */

      for ( r = c->local_variable; r != NULL; r = r->next )
	 {
	 if ( strcmp( r->name, c->name ) == 0 )
	    {
	    v = r;
	    }
	 }

      }

   /* now place value in expression stack */

   CURTASK exps[ CURTASK expsc ].type = (char) v->type;
   CURTASK exps[ CURTASK expsc ].pos_adv = position;

   switch( v->type )
      {
      case STRING:
	 CURTASK exps[ CURTASK expsc ].operation = CONST_STRING;

#if INTENSIVE_DEBUG
	 sprintf( bwb_ebuf, "in exp_ufnc(): ready to assign <%d>-byte STRING",
	    var_getsval( v )->length );
	 bwb_debug( bwb_ebuf );
#endif

	 str_btob( exp_getsval( &( CURTASK exps[ CURTASK expsc ] )),
	    var_getsval( v ) );

#if INTENSIVE_DEBUG
	 str_btoc( tbuf, var_getsval( v ) );
	 sprintf( bwb_ebuf, "in exp_ufnc(): string assigned <%s>", tbuf );
	 bwb_debug( bwb_ebuf );
#endif

	 break;

      default:
	 CURTASK exps[ CURTASK expsc ].operation = NUMBER;
	 CURTASK exps[ CURTASK expsc ].nval = var_getnval( v );
	 break;
      }

   return TRUE;

   }

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

	FUNCTION:       fslt_addlocalvar()

	DESCRIPTION:    This function adds a local variable
			to the FUNCTION-SUB lookup table at
			a specific level.

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

#if ANSI_C
int
fslt_addlocalvar( struct fslte *f, struct bwb_variable *v )
#else
int
fslt_addlocalvar( f, v )
   struct fslte *f;
   struct bwb_variable *v;
#endif
   {
   struct bwb_variable *c, *p;
#if INTENSIVE_DEBUG
   register int i;
#endif

   /* find end of local chain */

   if ( f->local_variable == NULL )
      {
#if INTENSIVE_DEBUG
      i = 0;
#endif
      f->local_variable = v;
      }
   else
      {
#if INTENSIVE_DEBUG
      i = 1;
#endif
      p = f->local_variable;
      for ( c = f->local_variable->next; c != NULL; c = c->next )
         {
         p = c;
#if INTENSIVE_DEBUG
         ++i;
#endif
         }
      p->next = v;
      }

   v->next = NULL;

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in fslt_addlocalvar(): added local variable variable <%s> arg number <%d>",
      v->name, i );
   bwb_debug( bwb_ebuf );
   getchar();
#endif

   return TRUE;

   }

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

	FUNCTION:       fslt_init()

	DESCRIPTION:    This function initializes the FUNCTION-SUB
			lookup table.

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

#if ANSI_C
int
fslt_init( int task )
#else
int
fslt_init( task )
   int task;
#endif
   {
   LOCALTASK fslt_start.next = &(LOCALTASK fslt_end);
   return TRUE;
   }

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

	FUNCTION:       is_label()

	DESCRIPTION:    This function determines whether the string
			pointed to by 'buffer' is a label (i.e.,
			ends with colon).

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

#if ANSI_C
extern int
is_label( char *buffer )
#else
int
is_label( buffer )
   char *buffer;
#endif
   {

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in is_label(): check element <%s>", buffer );
   bwb_debug( bwb_ebuf );
#endif

   if ( buffer[ strlen( buffer ) - 1 ] == ':' )
      {
      return TRUE;
      }
   else
      {
      return FALSE;
      }

   }


