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

        bwb_prn.c       Print Commands
                        for Bywater BASIC Interpreter

                        Copyright (c) 1992, Ted A. Campbell

                        Bywater Software
                        P. O. Box 4023
                        Duke Station
                        Durham, NC  27706

                        email: tcamp@acpub.duke.edu

        Copyright and Permissions Information:

        All U.S. and international copyrights are claimed by the
        author. The author grants permission to use this code
        and software based on it under the following conditions:
        (a) in general, the code and software based upon it may be
        used by individuals and by non-profit organizations; (b) it
        may also be utilized by governmental agencies in any country,
        with the exception of military agencies; (c) the code and/or
        software based upon it may not be sold for a profit without
        an explicit and specific permission from the author, except
        that a minimal fee may be charged for media on which it is
        copied, and for copying and handling; (d) the code must be
        distributed in the form in which it has been released by the
        author; and (e) the code and software based upon it may not
        be used for illegal activities.

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

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <math.h>

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

/* Prototypes for functions visible only to this file */

static int prn_cr( char *buffer, FILE *f );
static int prn_col = 1;
static int prn_width = 80;	/* default width for stdout */
static struct bwb_variable * bwb_esetovar( struct exp_ese *e );

struct prn_fmt
   {
   int type;			/* STRING, DOUBLE, SINGLE, or INTEGER */
   int exponential;		/* TRUE = use exponential notation */
   int right_justified;		/* TRUE = right justified else left justified */
   int width;			/* width of main section */
   int precision;		/* width after decimal point */
   int commas;			/* use commas every three steps */
   int sign;			/* prefix sign to number */
   int money;			/* prefix money sign to number */
   int fill;			/* ASCII value for fill character, normally ' ' */
   int minus;			/* postfix minus sign to number */
   };

static struct prn_fmt *get_prnfmt( char *buffer, int *position, FILE *f );
static int bwb_xerror( char *message );
static int xxputc( FILE *f, char c );

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

        FUNCTION:       bwb_print()

        DESCRIPTION:    This function implements the BASIC PRINT
                        command.

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

struct bwb_line *
bwb_print( struct bwb_line *l )
   {
   FILE *fp;
   static int pos;
   int req_devnumber;
   struct exp_ese *v;
   static char *s_buffer;          	/* small, temporary buffer */
   static int init = FALSE;

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_print(): enter function" );
   bwb_debug( bwb_ebuf );
#endif

   /* initialize buffers if necessary */

   if ( init == FALSE )
      {
      init = TRUE;
      if ( ( s_buffer = calloc( MAXSTRINGSIZE + 1, sizeof(char) ) ) == NULL )
         {
         bwb_error( err_getmem );
         }
      }

   /* advance beyond whitespace and check for the '#' sign */
   
   adv_ws( l->buffer, &( l->position ) );
   
   if ( l->buffer[ l->position ] == '#' )
      {
      ++( l->position );
      adv_element( l->buffer, &( l->position ), s_buffer );
      pos = 0;
      v = bwb_exp( s_buffer, FALSE, &pos );
      adv_ws( l->buffer, &( l->position ) );
      if ( l->buffer[ l->position ] == ',' )
         {
         ++( l->position );
         }
      else
         {
#if PROG_ERRORS
         bwb_error( "in bwb_print(): no comma after #n" );
#else
         bwb_error( err_syntax );
#endif
         l->next->position = 0;
         return l->next;
         }

      req_devnumber = exp_getival( v );

      /* check the requested device number */
      
      if ( ( req_devnumber < 0 ) || ( req_devnumber >= DEF_DEVICES ))
         {
#if PROG_ERRORS
         bwb_error( "in bwb_input(): Requested device number is out of range." );
#else
         bwb_error( err_devnum );
#endif
         l->next->position = 0;
         return l->next;
         }

      if (( dev_table[ req_devnumber ].mode == DEVMODE_CLOSED ) ||
         ( dev_table[ req_devnumber ].mode == DEVMODE_AVAILABLE ))
         {
#if PROG_ERRORS
         bwb_error( "in bwb_input(): Requested device number is not open." );
#else
         bwb_error( err_devnum );
#endif

         l->next->position = 0;
         return l->next;
         }

      if ( dev_table[ req_devnumber ].mode != DEVMODE_OUTPUT )
         {
#if PROG_ERRORS
         bwb_error( "in bwb_print(): Requested device is not open for OUTPUT." );
#else
         bwb_error( err_devnum );
#endif

         l->next->position = 0;
         return l->next;
         }

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_print(): device number is <%d>",
         req_devnumber );
      bwb_debug( bwb_ebuf );
#endif

      /* look up the requested device in the device table */

      fp = dev_table[ req_devnumber ].cfp;

      }

   else
      {
      fp = stdout;
      }

   bwb_xprint( l, fp );

   l->next->position = 0;
   return l->next;
   }

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

        FUNCTION:       bwb_xprint()

        DESCRIPTION:

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

int
bwb_xprint( struct bwb_line *l, FILE *f )
   {
   struct exp_ese *e;
   int loop;
   static int p;
   static int fs_pos;
   struct prn_fmt *format;
   static char *format_string;
   static char *output_string;
   static char *element;
   static char *prnbuf;
   static int init = FALSE;
#if INTENSIVE_DEBUG || TEST_BSTRING
   bstring *b;
#endif

   /* initialize buffers if necessary */

   if ( init == FALSE )
      {
      init = TRUE;
      if ( ( format_string = calloc( MAXSTRINGSIZE + 1, sizeof(char) ) ) == NULL )
         {
         bwb_error( err_getmem );
         }
      if ( ( output_string = calloc( MAXSTRINGSIZE + 1, sizeof(char) ) ) == NULL )
         {
         bwb_error( err_getmem );
         }      
      if ( ( element = calloc( MAXSTRINGSIZE + 1, sizeof(char) ) ) == NULL )
         {
         bwb_error( err_getmem );
         }      
      if ( ( prnbuf = calloc( MAXSTRINGSIZE + 1, sizeof(char) ) ) == NULL )
         {
         bwb_error( err_getmem );
         }      
      }

   /* Detect USING Here */

   fs_pos = -1;

   /* get "USING" in format_string */

   p = l->position;
   adv_element( l->buffer, &p, format_string );
   bwb_strtoupper( format_string );

   /* check to be sure */

   if ( strcmp( format_string, "USING" ) == 0 )
      {
      l->position = p;
      adv_ws( l->buffer, &( l->position ) );

      /* now get the format string in format_string */

      e = bwb_exp( l->buffer, FALSE, &( l->position ) );
      if ( e->type == STRING )
         {

         /* copy the format string to buffer */

         str_btoc( format_string, exp_getsval( e ) );

         /* look for ';' after format string */

         fs_pos = 0;
         adv_ws( l->buffer, &( l->position ) );
         if ( l->buffer[ l->position ] == ';' )
            {
            ++l->position;
            adv_ws( l->buffer, &( l->position ) );
            }
         else
            {
#if PROG_ERRORS
            bwb_error( "Failed to find \";\" after format string in PRINT USING" );
#else
            bwb_error( err_syntax );
#endif
            return FALSE;
            }

#if INTENSIVE_DEBUG
         sprintf( bwb_ebuf, "in bwb_xprint(): Found USING, format string <%s>",
            format_string );
         bwb_debug( bwb_ebuf );
#endif

         }

      else
         {
#if PROG_ERRORS
         bwb_error( "Failed to find format string after PRINT USING" );
#else
         bwb_error( err_syntax );
#endif
         return FALSE;
         }
      }

   /* if no arguments, simply print CR and return */

   adv_ws( l->buffer, &( l->position ) );
   switch( l->buffer[ l->position ] )
      {
      case '\0':
      case '\n':
      case '\r':
      case ':':
         xprintf( f, "\n" );
         return TRUE;
      default:
         break;
      }

   /* LOOP THROUGH PRINT ELEMENTS */

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

      /* resolve the string */

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

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_xprint(): op <%d> type <%c>",
         e->operation, e->type );
      bwb_debug( bwb_ebuf );
#endif

      /* an OP_NULL probably indicates a terminating ';', but this
         will be detected later, so we can ignore it for now */

      if ( e->operation != OP_NULL )
         {
#if TEST_BSTRING
         b = exp_getsval( e );
         sprintf( bwb_ebuf, "in bwb_xprint(): bstring name is <%s>",
            b->name );
         bwb_debug( bwb_ebuf );
#endif
         str_btoc( element, exp_getsval( e ) );
         }
      else
         {
         element[ 0 ] = '\0';
         }

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

      /* print with format if there is one */

      if (( fs_pos > -1 ) && ( strlen( element ) > 0 ))
         {
         format = get_prnfmt( format_string, &fs_pos, f );

#if INTENSIVE_DEBUG
         sprintf( bwb_ebuf, "in bwb_xprint(): format type <%c> width <%d>",
            format->type, format->width );
         bwb_debug( bwb_ebuf );
#endif

         switch( format->type )
            {
            case STRING:
               if ( e->type != STRING )
                  {
#if PROG_ERRORS
                  bwb_error( "Type mismatch in PRINT USING" );
#else
                  bwb_error( err_mismatch );
#endif
                  }
               sprintf( output_string, "%.*s", format->width,
                  element );

#if INTENSIVE_DEBUG
               sprintf( bwb_ebuf, "in bwb_xprint(): output string <%s>",
                  output_string );
               bwb_debug( bwb_ebuf );
#endif

               xprintf( f, output_string );
               break;
            case INTEGER:
               if ( e->type == STRING )
                  {
#if PROG_ERRORS
                  bwb_error( "Type mismatch in PRINT USING" );
#else
                  bwb_error( err_mismatch );
#endif
                  }
               sprintf( output_string, "%.*d", format->width,
                  exp_getival( e ) );
               xprintf( f, output_string );
               break;
            case SINGLE:
            case DOUBLE:
               if ( e->type == STRING )
                  {
#if PROG_ERRORS
                  bwb_error( "Type mismatch in PRINT USING" );
#else
                  bwb_error( err_mismatch );
#endif
                  }
               if ( format->exponential == TRUE )
                  {
                  sprintf( output_string, "%.le", 
                     e->dval );
                  xprintf( f, output_string );
                  }
               else
                  {
                  sprintf( output_string, "%*.*lf", 
                     format->width + 1 + format->precision,
                     format->precision, e->dval );
                  xprintf( f, output_string );
                  }
               break;
            default:
#if PROG_ERRORS
               sprintf( bwb_ebuf, "in bwb_xprint(): get_prnfmt() returns unknown type <%c>",
                  format->type );
               bwb_error( bwb_ebuf );
#else
               bwb_error( err_mismatch );
#endif
               break;
            }
         }

      /* not a format string: use defaults */

      else if ( strlen( element ) > 0 )
         {

         switch( e->type )
            {
            case STRING:
               xprintf( f, element );
               break;
            case INTEGER:
               sprintf( prnbuf, " %d", exp_getival( e ) );
               xprintf( f, prnbuf );
               break;
            case DOUBLE:
               sprintf( prnbuf, " %.*f", prn_precision( bwb_esetovar( e )), 
                  exp_getdval( e ) );
               xprintf( f, prnbuf );
               break;
            default:
               sprintf( prnbuf, " %.*f", prn_precision( bwb_esetovar( e )), 
                  exp_getfval( e ) );
               xprintf( f, prnbuf );
               break;
            }
         }

      /* check the position to see if the loop should continue */

      adv_ws( l->buffer, &( l->position ) );
      switch( l->buffer[ l->position ] )
         {
         case ':':		/* end of line segment */
	    loop = FALSE;
/*	    ++( l->position ); */
	    break;
         case '\0':		/* end of buffer */
         case '\n':
         case '\r':
	    loop = FALSE;
            break;
         case ',':		/* tab over */
            xputc( f, '\t' );
            ++l->position;
            adv_ws( l->buffer, &( l->position ) );
            break;
         case ';':		/* concatenate strings */
            ++l->position;
            adv_ws( l->buffer, &( l->position ) );
            break;
         }

      }				/* end of loop through print elements */

   /* call prn_cr() to print a CR if it is not overridden by a
      concluding ';' mark */

   prn_cr( l->buffer, f ); 

   return TRUE;

   }                            /* end of function bwb_xprint() */

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

        FUNCTION:       get_prnfmt()

        DESCRIPTION:

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

struct prn_fmt *
get_prnfmt( char *buffer, int *position, FILE *f )
   {
   static struct prn_fmt retstruct;
   register int c;
   int loop;

   /* set some defaults */

   retstruct.type = FALSE;
   retstruct.exponential = FALSE;
   retstruct.right_justified = FALSE;
   retstruct.commas = FALSE;
   retstruct.sign = FALSE;
   retstruct.money = FALSE;
   retstruct.fill = ' ';
   retstruct.minus = FALSE;

   /* check for negative position */

   if ( *position < 0 )
      {
      return &retstruct;
      }

   /* advance past whitespace */

   adv_ws( buffer, position );

   /* check first character: a lost can be decided right here */

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

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

      switch( buffer[ *position ] )
         {
         case ' ':		/* end of this format segment */
            loop = FALSE;
            break;
         case '\0':		/* end of format string */
         case '\n':
         case '\r':
            *position = -1;
            return &retstruct;
         case '_':		/* print next character as literal */
            ++( *position );
            xputc( f, buffer[ *position ] );
            ++( *position );
            break;
         case '!':
            retstruct.type = STRING;
            retstruct.width = 1;
            return &retstruct;
         case '\\':
#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in get_prnfmt(): found \\" );
            bwb_debug( bwb_ebuf );
#endif
            retstruct.type = STRING;
            ++( *position );
            for ( retstruct.width = 0; buffer[ *position ] == ' '; ++( *position ) )
               {
               ++retstruct.width;
               }
            if ( buffer[ *position ] == '\\' )
               {
               ++( *position );
               }
            return &retstruct;
         case '$':
            ++( *position );
            retstruct.money = TRUE;
            if ( buffer[ *position ] == '$' )
               {
               ++( *position );
               }
            break;
         case '*':
            ++( *position );
            retstruct.fill = '*';
            if ( buffer[ *position ] == '*' )
               {
               ++( *position );
               }
            break;
         case '+':
            ++( *position );
            retstruct.sign = TRUE;
            break;
         case '#':
            retstruct.type = INTEGER;		/* for now */
            ++( *position );
            for ( retstruct.width = 1; buffer[ *position ] == '#'; ++( *position ) )
               {
               ++retstruct.width;
               }
            if ( buffer[ *position ] == ',' )
               {
               retstruct.commas = TRUE;
               }
            if ( buffer[ *position ] == '.' )
               {
               retstruct.type = DOUBLE;
               ++( *position );
               for ( retstruct.precision = 0; buffer[ *position ] == '#'; ++( *position ) )
                  {
                  ++retstruct.precision;
                  }
               }
            if ( buffer[ *position ] == '-' )
               {
               retstruct.minus = TRUE;
               ++( *position );
               }
            return &retstruct;
         case '^':
            retstruct.type = DOUBLE;
            retstruct.exponential = TRUE;
            for ( retstruct.width = 1; buffer[ *position ] == '^'; ++( *position ) )
               {
               ++retstruct.width;
               }
            return &retstruct;
         
         }
      }					/* end of loop */
      
   return &retstruct;
   }
   
/***************************************************************

        FUNCTION:       prn_cr()

        DESCRIPTION:

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

static int
prn_cr( char *buffer, FILE *f )
   {
   register int c;
   int loop;

   /* find the end of the buffer */

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

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in prn_cr(): initial c is <%d>", c );
   bwb_debug( bwb_ebuf );
#endif

   /* back up through any whitespace */

   loop = TRUE;
   while ( loop == TRUE )
      {
      switch( buffer[ c ] )
         {
         case ' ':                              /* if whitespace */
         case '\t':
         case 0:

#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in prn_cr(): backup: c is <%d>, char <%c>[0x%x]",
               c, buffer[ c ], buffer[ c ] );
            bwb_debug( bwb_ebuf );
#endif

            --c;                                /* back up */
            if ( c < 0 )                        /* check position */
               {
               loop = FALSE;
               }
            break;

         default:                               /* else break out */
#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in prn_cr(): breakout: c is <%d>, char <%c>[0x%x]",
               c, buffer[ c ], buffer[ c ] );
            bwb_debug( bwb_ebuf );
#endif
            loop = FALSE;
            break;
         }
      }

   if ( buffer[ c ] == ';' )
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in prn_cr(): concluding <;> detected." );
      bwb_debug( bwb_ebuf );
#endif

      return FALSE;
      }

   else
      {
      xprintf( f, "\n" );
      return TRUE;
      }

   }

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

        FUNCTION:       xprintf()

        DESCRIPTION:

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

int
xprintf( FILE *f, char *buffer )
   {
   char *p;

   /* DO NOT try anything so stupid as to run bwb_debug() from 
      here, because it will create an endless loop. And don't
      ask how I know. */

   for ( p = buffer; *p != '\0'; ++p )
      {
      xputc( f, *p );
      }

   return TRUE;
   }

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

        FUNCTION:       xputc()

        DESCRIPTION:

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

int
xputc( FILE *f, char c )
   {
   static int tab_pending = FALSE;
   register int i;

   /* check for pending TAB */

   if ( tab_pending == TRUE )
      {
      if ( (int) c < ( * prn_getcol( f ) ) )
         {
         xxputc( f, '\n' );
         }
      while( ( * prn_getcol( f )) < (int) c )
         {
         xxputc( f, ' ' );
         }
      tab_pending = FALSE;
      return TRUE;
      }

   /* check c for specific output options */

   switch( c )
      {
      case PRN_TAB:
         tab_pending = TRUE;
         break;

      case '\t':
         while( ( (* prn_getcol( f )) % 14 ) != 0 )
            {
            xxputc( f, ' ' );
            }
         break;

      default:
         xxputc( f, c );
         break;
      }

   return TRUE;

   }

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

        FUNCTION:       xxputc()

        DESCRIPTION:

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

int
xxputc( FILE *f, char c )
   {

   /* check to see if width has been exceeded */

   if ( * prn_getcol( f ) >= prn_getwidth( f ))
      {
      fputc( '\n', f );			/* output LF */
      * prn_getcol( f ) = 1;		/* and reset */
      }

   /* adjust the column counter */

   if ( c == '\n' )
      {
      * prn_getcol( f ) = 1;
      }
   else
      {
      ++( * prn_getcol( f ));
      }
      
   /* now output the character */

   return fputc( c, f );

   }

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

        FUNCTION:       prn_getcol()

        DESCRIPTION:

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

int *
prn_getcol( FILE *f )
   {
   register int n;
   static int dummy_pos;

   if (( f == stdout ) || ( f == stderr ))
      {
      return &prn_col;
      }

   for ( n = 0; n < DEF_DEVICES; ++n )
      {
      if ( dev_table[ n ].cfp == f )
         {
         return &( dev_table[ n ].col );
         }
      }

   /* search failed */

#if PROG_ERRORS
   bwb_error( "in prn_getcol(): failed to find file pointer" );
#else
   bwb_error( err_devnum );
#endif

   return &dummy_pos;

   }

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

        FUNCTION:       prn_getwidth()

        DESCRIPTION:

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

int
prn_getwidth( FILE *f )
   {
   register int n;

   if (( f == stdout ) || ( f == stderr ))
      {
      return prn_width;
      }

   for ( n = 0; n < DEF_DEVICES; ++n )
      {
      if ( dev_table[ n ].cfp == f )
         {
         return dev_table[ n ].width;
         }
      }

   /* search failed */

#if PROG_ERRORS
   bwb_error( "in prn_getwidth(): failed to find file pointer" );
#else
   bwb_error( err_devnum );
#endif

   return 1;

   }

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

        FUNCTION:       prn_precision()

        DESCRIPTION:

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

int
prn_precision( struct bwb_variable *v )
   {
   int max_precision = 6;
   double dval, d;
   int r;

   /* check for double value */

   if ( v->type == DOUBLE )
      {
      max_precision = 12;
      }

   /* get the value in dval */

   dval = var_getdval( v );

   /* cycle through until precision is found */

   d = 1.0;
   for ( r = 0; r < max_precision; ++r )
      {

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in prn_precision(): fmod( %f, %f ) = %.12f",
         dval, d, fmod( dval, d ) );
      bwb_debug( bwb_ebuf );
#endif

      if ( fmod( dval, d ) < 0.0000001 )
         {
         return r;
         }
      d /= 10;
      }

   /* return */

   return r;

   }

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

        FUNCTION:       fnc_tab()

        DESCRIPTION:    

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

struct bwb_variable *
fnc_tab( int argc, struct bwb_variable *argv )
   {
   static struct bwb_variable nvar;
   static int init = FALSE;
   static char t_string[ 4 ];
   static char nvar_name[] = "(tmp)";
   bstring *b;
   
   /* initialize nvar if necessary */

   if ( init == FALSE )
      {
      init = TRUE;
      var_make( &nvar, (int) STRING );
/*      nvar.name = nvar_name; */
      }

   /* check for correct number of parameters */

   if ( argc < 1 )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "Not enough parameters (%d) to function TAB().",
         argc );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_syntax );
#endif
      break_handler();
      return NULL;
      }
   else if ( argc > 1 )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "Too many parameters (%d) to function TAB().",
         argc );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_syntax );
#endif
      break_handler();
      return NULL;
      }

   t_string[ 0 ] = PRN_TAB;
   t_string[ 1 ] = (char) var_getival( &( argv[ 0 ] ));
   t_string[ 2 ] = '\0';

   b = var_getsval( &nvar );
   str_ctob( b, t_string );

   return &nvar;
   }

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

        FUNCTION:       fnc_spc()

        DESCRIPTION:    

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

struct bwb_variable *
fnc_spc( int argc, struct bwb_variable *argv )
   {
   return fnc_space( argc, argv );
   }

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

        FUNCTION:       fnc_space()

        DESCRIPTION:    

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

struct bwb_variable *
fnc_space( int argc, struct bwb_variable *argv )
   {
   static struct bwb_variable nvar;
   static char *tbuf;
   static int init = FALSE;
   int spaces;
   register int i;
   bstring *b;
   
   /* check for correct number of parameters */

   if ( argc < 1 )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "Not enough parameters (%d) to function SPACE$().",
         argc );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_syntax );
#endif
      break_handler();
      return NULL;
      }
   else if ( argc > 1 )
      {
#if PROG_ERRORS
      sprintf( bwb_ebuf, "Too many parameters (%d) to function SPACE$().",
         argc );
      bwb_error( bwb_ebuf );
#else
      bwb_error( err_syntax );
#endif
      break_handler();
      return NULL;
      }

   /* initialize nvar if necessary */

   if ( init == FALSE )
      {
      init = TRUE;
      var_make( &nvar, (int) STRING );
      if ( ( tbuf = calloc( MAXSTRINGSIZE + 1, sizeof( char ) )) == NULL )
         {
         bwb_error( err_getmem );
         }
      }

   tbuf[ 0 ] = '\0';
   spaces = var_getival( &( argv[ 0 ] ));

   /* add spaces to the string */

   for ( i = 0; i < spaces; ++i )
      {
      tbuf[ i ] = ' ';
      tbuf[ i + 1 ] = '\0';
      }

   b = var_getsval( &nvar );
   str_ctob( b, tbuf );

   return &nvar;
   }

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

        FUNCTION:       fnc_pos()

        DESCRIPTION:    

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

struct bwb_variable *
fnc_pos( int argc, struct bwb_variable *argv )
   {
   static struct bwb_variable nvar;
   static int init = FALSE;
   static char nvar_name[] = "<pos()>";

   /* initialize nvar if necessary */

   if ( init == FALSE )
      {
      init = TRUE;
      var_make( &nvar, (int) INTEGER );
/*      nvar.name = nvar_name; */
      }

   * var_findival( &nvar, nvar.array_pos ) = prn_col;

   return &nvar;
   }

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

        FUNCTION:       fnc_err()

        DESCRIPTION:    

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

struct bwb_variable *
fnc_err( int argc, struct bwb_variable *argv )
   {
   static struct bwb_variable nvar;
   static int init = FALSE;
   static char nvar_name[] = "<err()>";

   /* initialize nvar if necessary */

   if ( init == FALSE )
      {
      init = TRUE;
      var_make( &nvar, (int) INTEGER );
/*      nvar.name = nvar_name; */
      }

   * var_findival( &nvar, nvar.array_pos ) = err_number;

   return &nvar;
   }

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

        FUNCTION:       fnc_erl()

        DESCRIPTION:    

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

struct bwb_variable *
fnc_erl( int argc, struct bwb_variable *argv )
   {
   static struct bwb_variable nvar;
   static int init = FALSE;
   static char nvar_name[] = "<erl()>";

   /* initialize nvar if necessary */

   if ( init == FALSE )
      {
      init = TRUE;
      var_make( &nvar, (int) INTEGER );
/*      nvar.name = nvar_name; */
      }

   * var_findival( &nvar, nvar.array_pos ) = err_line;

   return &nvar;
   }

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

        FUNCTION:       bwb_debug()

        DESCRIPTION:    This function is called to display
                        debugging messages in Bywater BASIC.
                        It does not break out at the current
                        point (as bwb_error() does).

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

#if PERMANENT_DEBUG
int
bwb_debug( char *message )
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];

   fflush( stdout );
   fflush( errfdevice );
   if ( prn_col != 1 )
      {
      xprintf( errfdevice, "\n" );
      }
   sprintf( tbuf, "DEBUG %s\n", message );
   xprintf( errfdevice, tbuf );

   return TRUE;
   }
#endif

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

        FUNCTION:       bwb_lerror()

        DESCRIPTION:    This function implements the BASIC ERROR
                        command.

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

struct bwb_line *
bwb_lerror( struct bwb_line *l )
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];
   int n;

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_lerror(): entered function " );
   bwb_debug( bwb_ebuf );
#endif

   /* Check for argument */

   adv_ws( l->buffer, &( l->position ) );
   switch( l->buffer[ l->position ] )
      {
      case '\0':
      case '\n':
      case '\r':
      case ':':
         bwb_error( err_incomplete );
         l->next->position = 0;
         return l->next;
      default:
         break;
      }

   /* get the variable name or numerical constant */

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

#if INTENSIVE_DEBUG
   sprintf( bwb_ebuf, "in bwb_lerror(): error number is <%d> ", n );
   bwb_debug( bwb_ebuf );
#endif

   /* check the line number value */

   if ( ( n < 0 ) || ( n >= N_ERRORS ))
      {
      sprintf( bwb_ebuf, "Error number %d is out of range", n );
      bwb_xerror( bwb_ebuf );
      return l;
      }

   bwb_xerror( err_table[ n ] );

   return l;

   }

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

        FUNCTION:       bwb_error()

        DESCRIPTION:    This function is called to handle errors
                        in Bywater BASIC.  It displays the error
                        message, then calls the break_handler()
                        routine.

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

int
bwb_error( char *message )
   {
   register int e;
   static char tbuf[ MAXSTRINGSIZE + 1 ];	/* must be permanent */

   /* try to find the error message to identify the error number */

   err_line = bwb_number;		/* set error line number */
   for ( e = 0; e < N_ERRORS; ++e )
      {
      if ( message == err_table[ e ] )	/* set error number */
         {
         err_number = e;
         e = N_ERRORS;			/* break out of loop quickly */
         }
      }

   /* if err_gosubn is not set, then use xerror routine */

   if ( err_gosubn == 0 )
      {
      return bwb_xerror( message );
      }

   /* err_gosubn is not set; call user-defined error subroutine */

   sprintf( tbuf, "GOSUB %d", err_gosubn );
   cnd_xpline( bwb_l, tbuf );
   return TRUE;

   }

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

        FUNCTION:       bwb_xerror()

        DESCRIPTION:    This function is called by bwb_error()
                        in Bywater BASIC.  It displays the error
                        message, then calls the break_handler()
                        routine.

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

int
bwb_xerror( char *message )
   {
   static char tbuf[ MAXSTRINGSIZE + 1 ];	/* this memory should be 
						   permanent in case of memory
						   overrun errors */

   fflush( stdout );
   fflush( errfdevice );
   if ( prn_col != 1 )
      {
      xprintf( errfdevice, "\n" );
      }
   if ( bwb_number == 0 )
      {
      sprintf( tbuf, "\n%s: %s\n", ERRD_HEADER, message );
      }
   else
      {
      sprintf( tbuf, "\n%s %d: %s\n", ERROR_HEADER, bwb_number, message );
      }
   xprintf( errfdevice, tbuf );
   break_handler();

   return FALSE;
   }

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

        FUNCTION:       matherr()

        DESCRIPTION:    This function is called to handle math
                        errors in Bywater BASIC.  It displays
                        the error message, then calls the
                        break_handler() routine.

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

int
matherr( struct exception *except )
   {

   perror( MATHERR_HEADER );
   break_handler();

   return FALSE;
   }

static struct bwb_variable * 
bwb_esetovar( struct exp_ese *e )
   {
   static struct bwb_variable b;
   static init = FALSE;

   var_make( &b, e->type );

   switch( e->type )
      {
      case STRING:
         str_btob( var_findsval( &b, b.array_pos ), exp_getsval( e ) );
         break;
      case DOUBLE:
         * var_finddval( &b, b.array_pos ) = e->dval;
         break;
      case INTEGER:
         * var_findival( &b, b.array_pos ) = e->ival;
         break;
      default:
         * var_findfval( &b, b.array_pos ) = e->fval;
         break;
      }

   return &b;

   }

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

        FUNCTION:       bwb_width()

        DESCRIPTION:

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

struct bwb_line *
bwb_width( struct bwb_line *l )
   {
   int req_devnumber;
   int req_width;
   struct exp_ese *e;
   char tbuf[ MAXSTRINGSIZE + 1 ];
   int pos;

   /* detect device number if present */

   req_devnumber = -1;
   adv_ws( l->buffer, &( l->position ) );
   
   if ( l->buffer[ l->position ] == '#' )
      {
      ++( l->position );
      adv_element( l->buffer, &( l->position ), tbuf );
      pos = 0;
      e = bwb_exp( tbuf, FALSE, &pos );
      adv_ws( l->buffer, &( l->position ) );
      if ( l->buffer[ l->position ] == ',' )
         {
         ++( l->position );
         }
      else
         {
#if PROG_ERRORS
         bwb_error( "in bwb_width(): no comma after #n" );
#else
         bwb_error( err_syntax );
#endif
         l->next->position = 0;
         return l->next;
         }

      req_devnumber = exp_getival( e );

      /* check the requested device number */
      
      if ( ( req_devnumber < 0 ) || ( req_devnumber >= DEF_DEVICES ))
         {
#if PROG_ERRORS
         bwb_error( "in bwb_width(): Requested device number is out of range." );
#else
         bwb_error( err_devnum );
#endif
         l->next->position = 0;
         return l->next;
         }

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_width(): device number is <%d>",
         req_devnumber );
      bwb_debug( bwb_ebuf );
#endif

      }

   /* read the width requested */

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

   /* check the width */

   if ( ( req_width < 1 ) || ( req_width > 255 ))
      {
#if PROG_ERRORS
      bwb_error( "in bwb_width(): Requested width is out of range (1-255)" );
#else
      bwb_error( err_valoorange );
#endif
      }

   /* assign the width */

   if ( req_devnumber == -1 )
      {
      prn_width = req_width;
      }
   else
      {
      dev_table[ req_devnumber ].width = req_width;
      }

   /* return */

   return l->next;
   }

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

        FUNCTION:       bwb_write()

        DESCRIPTION:

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

struct bwb_line *
bwb_write( struct bwb_line *l )
   {
   struct exp_ese *e;
   int req_devnumber;
   int pos;
   FILE *fp;
   char tbuf[ MAXSTRINGSIZE + 1 ];
   int loop;
   static struct bwb_variable nvar;
   static int init = FALSE;

   /* initialize variable if necessary */

   if ( init == FALSE )
      {
      init = TRUE;
      var_make( &nvar, SINGLE );
      }

   /* detect device number if present */

   adv_ws( l->buffer, &( l->position ) );
   
   if ( l->buffer[ l->position ] == '#' )
      {
      ++( l->position );
      adv_element( l->buffer, &( l->position ), tbuf );
      pos = 0;
      e = bwb_exp( tbuf, FALSE, &pos );
      adv_ws( l->buffer, &( l->position ) );
      if ( l->buffer[ l->position ] == ',' )
         {
         ++( l->position );
         }
      else
         {
#if PROG_ERRORS
         bwb_error( "in bwb_write(): no comma after #n" );
#else
         bwb_error( err_syntax );
#endif
         l->next->position = 0;
         return l->next;
         }

      req_devnumber = exp_getival( e );

      /* check the requested device number */
      
      if ( ( req_devnumber < 0 ) || ( req_devnumber >= DEF_DEVICES ))
         {
#if PROG_ERRORS
         bwb_error( "in bwb_write(): Requested device number is out of range." );
#else
         bwb_error( err_devnum );
#endif
         l->next->position = 0;
         return l->next;
         }

      if (( dev_table[ req_devnumber ].mode == DEVMODE_CLOSED ) ||
         ( dev_table[ req_devnumber ].mode == DEVMODE_AVAILABLE ))
         {
#if PROG_ERRORS
         bwb_error( "in bwb_write(): Requested device number is not open." );
#else
         bwb_error( err_devnum );
#endif

         l->next->position = 0;
         return l->next;
         }

      if ( dev_table[ req_devnumber ].mode != DEVMODE_OUTPUT )
         {
#if PROG_ERRORS
         bwb_error( "in bwb_write(): Requested device is not open for OUTPUT." );
#else
         bwb_error( err_devnum );
#endif

         l->next->position = 0;
         return l->next;
         }

#if INTENSIVE_DEBUG
      sprintf( bwb_ebuf, "in bwb_write(): device number is <%d>",
         req_devnumber );
      bwb_debug( bwb_ebuf );
#endif

      /* look up the requested device in the device table */

      fp = dev_table[ req_devnumber ].cfp;

      }

   else
      {
      fp = stdout;
      }

   /* be sure there is an element to print */ 

   adv_ws( l->buffer, &( l->position ) );
   loop = TRUE;
   switch( l->buffer[ l->position ] )
      {
      case '\n':
      case '\r':
      case '\0':
      case ':':
         loop = FALSE;
         break;
      }

   /* loop through elements */

   while ( loop == TRUE )
      {

      /* get the next element */

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

      /* perform type-specific output */

      switch( e->type )
         {
         case STRING:
            xputc( fp, '\"' );
            str_btoc( tbuf, exp_getsval( e ) );
            xprintf( fp, tbuf );
            xputc( fp, '\"' );
#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in bwb_write(): output string element <\"%s\">",
               tbuf );
            bwb_debug( bwb_ebuf );
#endif
            break;
         default:
            * var_findfval( &nvar, nvar.array_pos ) =
               exp_getfval( e );
            sprintf( tbuf, " %.*f", prn_precision( &nvar ), 
               var_getfval( &nvar ) );
            xprintf( fp, tbuf );
#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in bwb_write(): output numerical element <%s>",
               tbuf );
            bwb_debug( bwb_ebuf );
#endif
            break;
         }				/* end of case for type-specific output */

      /* seek a comma at end of element */

      adv_ws( l->buffer, &( l->position ) );
      if ( l->buffer[ l->position ] == ',' )
         {
         xputc( fp, ',' );
         ++( l->position );
         }

      /* no comma: end the loop */

      else
         {
         loop = FALSE;
         }

      }					/* end of loop through elements */

   /* print LF */

   xputc( fp, '\n' );

   /* return */

   l->next->position = 0;
   return l->next;
   }

