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

	bwb_prn.c       Print and Error-Handling Commands
                        for Bywater BASIC Interpreter

                        Copyright (c) 1993, Ted A. Campbell
                        Bywater Software

                        email: tcamp@delphi.com

        Copyright and Permissions Information:

        All U.S. and international rights are claimed by the author,
        Ted A. Campbell.

	This software is released under the terms of the GNU General
	Public License (GPL), which is distributed with this software
	in the file "COPYING".  The GPL specifies the terms under
	which users may copy and use the software in this distribution.

	A separate license is available for commercial distribution,
	for information on which you should contact the author.

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

/*---------------------------------------------------------------*/
/* NOTE: Modifications marked "JBV" were made by Jon B. Volkoff, */
/* 11/1995 (eidetics@cerf.net).                                  */
/*---------------------------------------------------------------*/

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

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

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

int prn_col = 1;
static int prn_width = 80;	/* default width for stdout */

struct prn_fmt
   {
   int type;			/* STRING, NUMBER, SINGLE, or NUMBER */
   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 */
   };

#if ANSI_C
static int prn_cr( char *buffer, FILE *f );
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 );
static int xxxputc( FILE *f, char c );
static struct bwb_variable * bwb_esetovar( struct exp_ese *e );
#else
static int prn_cr();
static struct prn_fmt *get_prnfmt();
static int bwb_xerror();
static int xxputc();
static int xxxputc();
static struct bwb_variable * bwb_esetovar();
#endif


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

        FUNCTION:       bwb_print()

        DESCRIPTION:    This function implements the BASIC PRINT
                        command.

	SYNTAX:		PRINT [# device-number,][USING format-string$;] expressions...

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

#if ANSI_C
struct bwb_line *
bwb_print( struct bwb_line *l )
#else
struct bwb_line *
bwb_print( l )
   struct bwb_line *l;
#endif
   {
   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;

      /* Revised to CALLOC pass-thru call by JBV */
      if ( ( s_buffer = CALLOC( MAXSTRINGSIZE + 1, sizeof(char), "bwb_print") ) == NULL )
         {
#if PROG_ERRORS
         bwb_error( "in bwb_print(): failed to get memory for s_buffer" );
#else
         bwb_error( err_getmem );
#endif
         }
      }

   /* advance beyond whitespace and check for the '#' sign */

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

#if COMMON_CMDS
   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
         return bwb_zline( l );
         }

      req_devnumber = (int) exp_getnval( 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
         return bwb_zline( l );
         }

      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

         return bwb_zline( l );
         }

      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

         return bwb_zline( l );
         }

#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;
      }

#else
   fp = stdout;
#endif				/* COMMON_CMDS */

   bwb_xprint( l, fp );

   return bwb_zline( l );
   }

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

        FUNCTION:       bwb_xprint()

	DESCRIPTION:    This function implements the BASIC PRINT
			command, utilizing a specified file our
			output device.

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

#if ANSI_C
int
bwb_xprint( struct bwb_line *l, FILE *f )
#else
int
bwb_xprint( l, f )
   struct bwb_line *l;
   FILE *f;
#endif
   {
   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;
   register int i, j; /* JBV */
   int dig_pos, dec_pos; /* JBV */
   char tbuf[ MAXSTRINGSIZE + 1 ]; /* JBV */
#if INTENSIVE_DEBUG || TEST_BSTRING
   bstring *b;
#endif

   /* initialize buffers if necessary */

   if ( init == FALSE )
      {
      init = TRUE;

      /* Revised to CALLOC pass-thru call by JBV */
      if ( ( format_string = CALLOC( MAXSTRINGSIZE + 1, sizeof(char), "bwb_xprint") ) == NULL )
         {
#if PROG_ERRORS
         bwb_error( "in bwb_xprint(): failed to get memory for format_string" );
#else
         bwb_error( err_getmem );
#endif
         }
      /* Revised to CALLOC pass-thru call by JBV */
      if ( ( output_string = CALLOC( MAXSTRINGSIZE + 1, sizeof(char), "bwb_xprint") ) == NULL )
         {
#if PROG_ERRORS
         bwb_error( "in bwb_xprint(): failed to get memory for output_string" );
#else
         bwb_error( err_getmem );
#endif
         }
      /* Revised to CALLOC pass-thru call by JBV */
      if ( ( element = CALLOC( MAXSTRINGSIZE + 1, sizeof(char), "bwb_xprint") ) == NULL )
         {
#if PROG_ERRORS
         bwb_error( "in bwb_xprint(): failed to get memory for element buffer" );
#else
         bwb_error( err_getmem );
#endif
         }
      /* Revised to CALLOC pass-thru call by JBV */
      if ( ( prnbuf = CALLOC( MAXSTRINGSIZE + 1, sizeof(char), "bwb_xprint") ) == NULL )
         {
#if PROG_ERRORS
         bwb_error( "in bwb_xprint(): failed to get memory for prnbuf" );
#else
         bwb_error( err_getmem );
#endif
         }
      }

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

#if COMMON_CMDS

   /* check to be sure */

   if ( strcmp( format_string, CMD_XUSING ) == 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;
         }
      }

#endif 			/* COMMON_CMDS */

   /* 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 ':':
         prn_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 <%d>",
         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 ))
         {

#if COMMON_CMDS

         format = get_prnfmt( format_string, &fs_pos, f );

#if INTENSIVE_DEBUG
	 sprintf( bwb_ebuf, "in bwb_xprint(): format type <%d> 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
                  }
               if ( format->width == -1 ) /* JBV */
                  sprintf( output_string, "%s", element );
               else 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

               prn_xxprintf( f, output_string ); /* Was prn_xprintf (JBV) */
               break;

	    case NUMBER:
               if ( e->type == STRING )
                  {
#if PROG_ERRORS
                  bwb_error( "Type mismatch in PRINT USING" );
#else
                  bwb_error( err_mismatch );
#endif
                  }

	       if ( format->exponential == TRUE )
		  {
                  /*------------------------------------------------------*/
                  /* NOTE: Width and fill have no effect on C exponential */
                  /* format (JBV)                                         */
                  /*------------------------------------------------------*/
		  if ( format->sign == TRUE ) /* Added by JBV */
                     sprintf( output_string, "%+e", exp_getnval( e ) );
		  else
                     sprintf( output_string, "%e", exp_getnval( e ) );
		  }
	       else
		  {
                  /*---------------------------------------------------*/
                  /* NOTE: Minus, commas, and money are only valid for */
                  /* floating point format (JBV)                       */
                  /*---------------------------------------------------*/
		  if ( format->sign == TRUE ) /* Added by JBV */
		  sprintf( output_string, "%+*.*f",
		     format->width, format->precision, exp_getnval( e ) );
		  else if ( format->minus == TRUE ) /* Added by JBV */
                  {
		      sprintf( output_string, "%*.*f",
		         format->width, format->precision, exp_getnval( e ) );
                      for (i = 0; i < strlen( output_string ); ++i )
                      {
                          if ( output_string[ i ] != ' ' )
                          {
                              if ( output_string[ i ] == '-' )
                              {
                                  output_string[ i ] = ' ';
                                  strcat( output_string, "-" );
                              }
                              else strcat( output_string, " " );
                              break;
                          }
                      }
                  }
		  else
		  sprintf( output_string, "%*.*f",
		     format->width, format->precision, exp_getnval( e ) );

                  if ( format->commas == TRUE ) /* Added by JBV */
                  {
                      dig_pos = -1;
                      dec_pos = -1;
                      for ( i = 0; i < strlen( output_string ); ++i )
                      {
                          if ( ( isdigit( output_string[ i ] ) != 0 )
                          && ( dig_pos == -1 ) )
                             dig_pos = i;
                          if ( ( output_string[ i ] == '.' )
                          && ( dec_pos == -1 ) )
                             dec_pos = i;
                          if ( ( dig_pos != -1 ) && ( dec_pos != -1 ) ) break;
                      }
                      if ( dec_pos == -1 ) dec_pos = strlen( output_string );
                      j = 0;
                      for ( i = 0; i < strlen( output_string ); ++i )
                      {
                          if ( ( ( dec_pos - i ) % 3 == 0 )
                          && ( i > dig_pos ) && ( i < dec_pos ) )
                          {
                              tbuf[ j ] = ',';
                              ++j;
                              tbuf[ j ] = '\0';
                          }
                          tbuf[ j ] = output_string[ i ];
                          ++j;
                          tbuf[ j ] = '\0';
                      }
                      strcpy( output_string,
                         &tbuf[ strlen( tbuf ) - strlen( output_string ) ] );
                  }

                  if ( format->money == TRUE ) /* Added by JBV */
                  {
                      for ( i = 0; i < strlen( output_string ); ++i )
                      {
                          if ( output_string[ i ] != ' ' )
                          {
                              if ( i > 0 )
                              {
                                  if ( isdigit( output_string[ i ] ) == 0 )
                                  {
                                      output_string[ i - 1 ]
                                         = output_string[ i ];
                                      output_string[ i ] = '$';
                                  }
                                  else output_string[ i - 1 ] = '$';
                              }
                              break;
                          }
                      }
                  }

		  }

                  if ( format->fill == '*' ) /* Added by JBV */
                  for ( i = 0; i < strlen( output_string ); ++i )
                  {
                     if ( output_string[ i ] != ' ' ) break;
                     output_string[ i ] = '*';
                  }

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

               prn_xxprintf( f, output_string ); /* Was prn_xprintf (JBV) */
               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;
            }

#endif			/* COMMON_CMDS */

         }

      /* not a format string: use defaults */

      else if ( strlen( element ) > 0 )
         {

         switch( e->type )
            {
            case STRING:
               prn_xprintf( f, element );
               break;
            default:
#if NUMBER_DOUBLE
               sprintf( prnbuf, " %.*lf", prn_precision( bwb_esetovar( e )),
                  exp_getnval( e ) );
#else
               sprintf( prnbuf, " %.*f", prn_precision( bwb_esetovar( e )),
                  exp_getnval( e ) );
#endif
               prn_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 ] )
         {
#if OLDSTUFF
         case ':':		/* end of line segment */
	    loop = FALSE;
	    break;
         case '\0':		/* end of buffer */
         case '\n':
         case '\r':
	    loop = FALSE;
            break;
#endif
         case ',':		/* tab over */
            /* Tab only if there's no format specification! (JBV) */
            if (( fs_pos == -1 ) || ( strlen( element ) == 0 ))
               xputc( f, '\t' );
            ++l->position;
            adv_ws( l->buffer, &( l->position ) );
            break;
         case ';':		/* concatenate strings */
            ++l->position;
            adv_ws( l->buffer, &( l->position ) );
            break;
         default:
            loop = FALSE;
            break;
         }

      }				/* end of loop through print elements */

   if (( fs_pos > -1 ) && ( strlen( element ) > 0 ))
      format = get_prnfmt( format_string, &fs_pos, f ); /* Finish up (JBV) */

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

#if COMMON_CMDS

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

        FUNCTION:       get_prnfmt()

	DESCRIPTION:    This function gets the PRINT USING
			format string, returning a structure
			to the format.

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

#if ANSI_C
static struct prn_fmt *
get_prnfmt( char *buffer, int *position, FILE *f )
#else
static struct prn_fmt *
get_prnfmt( buffer, position, f )
   char *buffer;
   int *position;
   FILE *f;
#endif
   {
   static struct prn_fmt retstruct;
   int loop;

   /* set some defaults */

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

   /* check for negative position */

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

   /* advance past whitespace */

   /* adv_ws( buffer, position ); */  /* Don't think we want this (JBV) */

   /* 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 */
            xxputc( f, buffer[ *position ] ); /* Gotta output it (JBV) */
            ++( *position ); /* JBV */
            if (retstruct.type != FALSE) loop = FALSE; /* JBV */
            break;
         case '\0':		/* end of format string */
         case '\n':
         case '\r':
            *position = -1;
            return &retstruct;
         case '_':		/* print next character as literal */
            ++( *position );
            xxputc( f, buffer[ *position ] ); /* Not xputc, no tabs (JBV) */
            ++( *position );
            break;

	 case '!':
            retstruct.type = STRING;
            retstruct.width = 1;
            ++( *position ); /* JBV */
            return &retstruct;

	 case '&': /* JBV */
            retstruct.type = STRING;
            retstruct.width = -1; 
            ++( *position );
            return &retstruct;

	 case '\\':

#if INTENSIVE_DEBUG
            sprintf( bwb_ebuf, "in get_prnfmt(): found \\" );
            bwb_debug( bwb_ebuf );
#endif

	    retstruct.type = STRING;
	    ++retstruct.width;
	    ++( *position );
	    for ( ; buffer[ *position ] == ' '; ++( *position ) )
               {
               ++retstruct.width;
               }
            if ( buffer[ *position ] == '\\' )
	       {
	       ++retstruct.width;
               ++( *position );
               }
            return &retstruct;
         case '$':
            ++retstruct.width; /* JBV */
            ++( *position );
            retstruct.money = TRUE;
            if ( buffer[ *position ] == '$' )
               {
               ++retstruct.width; /* JBV */
               ++( *position );
               }
            break;
         case '*':
            ++retstruct.width; /* JBV */
            ++( *position );
            retstruct.fill = '*';
            if ( buffer[ *position ] == '*' )
               {
               ++retstruct.width; /* JBV */
               ++( *position );
               }
            break;
         case '+':
            ++( *position );
            retstruct.sign = TRUE;
            break;
         case '#':
            retstruct.type = NUMBER;		/* for now */
            /* ++( *position ); */  /* Removed by JBV */
            /* The initial condition shouldn't be retstruct.width = 1 (JBV) */
            for ( ; buffer[ *position ] == '#'; ++( *position ) )
               {
               ++retstruct.width;
               }
            if ( buffer[ *position ] == ',' )
               {
               retstruct.commas = TRUE;
               ++retstruct.width; /* JBV */
               ++( *position ); /* JBV */
               }
            if ( buffer[ *position ] == '.' )
               {
	       retstruct.type = NUMBER;
	       ++retstruct.width;
               ++( *position );
               for ( retstruct.precision = 0; buffer[ *position ] == '#'; ++( *position ) )
                  {
		  ++retstruct.precision;
		  ++retstruct.width;
                  }
               }
            if ( buffer[ *position ] == '-' )
               {
               retstruct.minus = TRUE;
               ++( *position );
               }
            return &retstruct;

	 case '^':
            retstruct.type = NUMBER;
            retstruct.exponential = TRUE;
            for ( retstruct.width = 1; buffer[ *position ] == '^'; ++( *position ) )
               {
               ++retstruct.width;
               }
            return &retstruct;

	 default: /* JBV */
            xxputc( f, buffer[ *position ] ); /* Gotta output it (JBV) */
            ++( *position );
            break;

         }
      }					/* end of loop */

   return &retstruct;
   }

#endif

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

        FUNCTION:       prn_cr()

	DESCRIPTION:    This function outputs a carriage-return
			to a specified file or output device.

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

#if ANSI_C
static int
prn_cr( char *buffer, FILE *f )
#else
static int
prn_cr( buffer, f )
   char *buffer;
   FILE *f;
#endif
   {
   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
      {
      prn_xprintf( f, "\n" );
      return TRUE;
      }

   }

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

        FUNCTION:       prn_xprintf()

	DESCRIPTION:    This function outputs a null-terminated
			string to a specified file or output
			device.

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

#if ANSI_C
int
prn_xprintf( FILE *f, char *buffer )
#else
int
prn_xprintf( f, buffer )
   FILE *f;
   char *buffer;
#endif
   {
   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:       prn_xxprintf()

	DESCRIPTION:    This function outputs a null-terminated
			string to a specified file or output
			device without expanding tabs.
			Added by JBV 10/95

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

#if ANSI_C
int
prn_xxprintf( FILE *f, char *buffer )
#else
int
prn_xxprintf( f, buffer )
   FILE *f;
   char *buffer;
#endif
   {
   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 )
      {
      xxputc( f, *p );
      }

   return TRUE;

   }

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

        FUNCTION:       xputc()

	DESCRIPTION:    This function outputs a character to a
			specified file or output device, expanding
			TABbed output approriately.

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

#if ANSI_C
int
xputc( FILE *f, char c )
#else
int
xputc( f, c )
   FILE *f;
   char c;
#endif
   {
   static int tab_pending = FALSE;

   /*--------------------------------------------------------------------*/
   /* Don't expand tabs if not printing to stdout or stderr (JBV 9/4/97) */
   /*--------------------------------------------------------------------*/
   if (( f != stdout ) && ( f != stderr ))
      {
      xxputc( f, c );
      return TRUE;
      }

   /* 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:    This function outputs a character to a
			specified file or output device, checking
			to be sure the PRINT width is within
			the bounds specified for that device.

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

#if ANSI_C
static int
xxputc( FILE *f, char c )
#else
static int
xxputc( f, c )
   FILE *f;
   char c;
#endif
   {

   /*--------------------------------------------------------------------*/
   /* Don't check width if not printing to stdout or stderr (JBV 9/4/97) */
   /*--------------------------------------------------------------------*/
   if (( f != stdout ) && ( f != stderr ))
      {
      return xxxputc( f, c );
      }

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

   if ( * prn_getcol( f ) >= prn_getwidth( f ))
      {
      xxxputc( f, '\n' );                 /* 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 xxxputc( f, c );

   }

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

	FUNCTION:       xxxputc()

	DESCRIPTION:    This function sends a character to a
			specified file or output device.

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

#if ANSI_C
static int
xxxputc( FILE *f, char c )
#else
static int
xxxputc( f, c )
   FILE *f;
   char c;
#endif
   {
   if (( f == stdout ) || ( f == stderr ))
      {
      return bwx_putc( c );
      }
   else
      {
      return fputc( c, f );
      }
   }

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

        FUNCTION:       prn_getcol()

	DESCRIPTION:    This function returns a pointer to an
			integer containing the current PRINT
			column for a specified file or device.

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

#if ANSI_C
int *
prn_getcol( FILE *f )
#else
int *
prn_getcol( f )
   FILE *f;
#endif
   {
   register int n;
   static int dummy_pos;

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

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

   /* 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:    This function returns the PRINT width for
			a specified file or output device.

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

#if ANSI_C
int
prn_getwidth( FILE *f )
#else
int
prn_getwidth( f )
   FILE *f;
#endif
   {
   register int n;

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

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

   /* 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:    This function returns the level of precision
			required for a specified numerical value.

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

#if ANSI_C
int
prn_precision( struct bwb_variable *v )
#else
int
prn_precision( v )
   struct bwb_variable *v;
#endif
   {
   int max_precision = 6;
   bnumber nval, d;
   int r;

   /* check for double value */

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

   /* get the value in nval */

   nval = (bnumber) fabs( (double) var_getnval( v ) );

   /* cycle through until precision is found */

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

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

      if ( fmod( (double) nval, (double) d ) < 0.0000001 ) /* JBV */
         {
         return r;
         }
      d /= 10;
      }

   /* return */

   return r;

   }

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

        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

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

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

   return TRUE;
   }
#endif

#if COMMON_CMDS

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

        FUNCTION:       bwb_lerror()

        DESCRIPTION:    This function implements the BASIC ERROR
                        command.

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

#if ANSI_C
struct bwb_line *
bwb_lerror( struct bwb_line *l )
#else
struct bwb_line *
bwb_lerror( l )
   struct bwb_line *l;
#endif
   {
   char tbuf[ MAXSTRINGSIZE + 1 ];
   int n;
   struct exp_ese *e; /* JBV */
   int pos; /* JBV */

#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 );
         return bwb_zline( l );
      default:
         break;
      }

   /* get the variable name or numerical constant */

   adv_element( l->buffer, &( l->position ), tbuf );
   /* n = atoi( tbuf ); */  /* Removed by JBV */

   /* Added by JBV */
   pos = 0;
   e = bwb_exp( tbuf, FALSE, &pos );
   n = (int) exp_getnval( e );

#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 bwb_zline( l );
      }

   bwb_xerror( err_table[ n ] );

   return bwb_zline( l );

   }

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

        FUNCTION:       bwb_width()

	DESCRIPTION:    This C function implements the BASIC WIDTH
			command, setting the maximum output width
			for a specified file or output device.

	SYNTAX:		WIDTH [# device-number,] number

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

#if ANSI_C
struct bwb_line *
bwb_width( struct bwb_line *l )
#else
struct bwb_line *
bwb_width( l )
   struct bwb_line *l;
#endif
   {
   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
         return bwb_zline( l );
         }

      req_devnumber = (int) exp_getnval( 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
         return bwb_zline( l );
         }

#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 = (int) exp_getnval( 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 bwb_zline( l );
   }

#endif			/* COMMON_CMDS */

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

        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.

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

#if ANSI_C
int
bwb_error( char *message )
#else
int
bwb_error( message )
   char *message;
#endif
   {
   register int e;
   static char tbuf[ MAXSTRINGSIZE + 1 ];	/* must be permanent */
   static struct bwb_line eline;
   int save_elevel;
   struct bwb_line *cur_l;
   int cur_mode;

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

   err_number = -1;			/* just for now */
   err_line = CURTASK 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 */
         }
      }

   /* set the position in the current line to the end */

   while( is_eol( bwb_l->buffer, &( bwb_l->position ) ) != TRUE )
      {
      ++( bwb_l->position );
      }

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

   if ( strlen( err_gosubl ) == 0 )
      {
      return bwb_xerror( message );
      }

#if INTENSIVE_DEBUG
   fprintf( stderr, "!!!!! USER_CALLED ERROR HANDLER\n" );
#endif

   /* save line and mode */

   cur_l = bwb_l;
   cur_mode = CURTASK excs[ CURTASK exsc ].code;

   /* err_gosubl is set; call user-defined error subroutine */

   sprintf( tbuf, "%s %s", CMD_GOSUB, err_gosubl );
   eline.next = &CURTASK bwb_end;
   eline.position = 0;
   eline.marked = FALSE;
   eline.buffer = tbuf;
   bwb_setexec( &eline, 0, EXEC_NORM );

   /* must be executed now */

   save_elevel = CURTASK exsc;
   bwb_execline();              /* This is a call to GOSUB and will increment
				   the exsc counter above save_elevel */

   while ( CURTASK exsc != save_elevel )        /* loop until return from GOSUB loop */
      {
      bwb_execline();
      }

   cur_l->next->position = 0;
   bwb_setexec( cur_l->next, 0, cur_mode );

   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.

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

#if ANSI_C
static int
bwb_xerror( char *message )
#else
static int
bwb_xerror( message )
   char *message;
#endif
   {

   bwx_errmes( message );

   break_handler();

   return FALSE;
   }

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

        FUNCTION:       bwb_esetovar()

        DESCRIPTION:    This function converts the value in expression
			stack 'e' to a bwBASIC variable structure.

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

#if ANSI_C
static struct bwb_variable *
bwb_esetovar( struct exp_ese *e )
#else
static struct bwb_variable *
bwb_esetovar( e )
   struct exp_ese *e;
#endif
   {
   static struct bwb_variable b;

   var_make( &b, e->type );

   switch( e->type )
      {
      case STRING:
         str_btob( var_findsval( &b, b.array_pos ), exp_getsval( e ) );
         break;
      default:
         * var_findnval( &b, b.array_pos ) = e->nval;
         break;
      }

   return &b;

   }

#if COMMON_CMDS

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

        FUNCTION:       bwb_write()

	DESCRIPTION:    This C function implements the BASIC WRITE
			command.

	SYNTAX:		WRITE [# device-number,] element [, element ]....

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

#if ANSI_C
struct bwb_line *
bwb_write( struct bwb_line *l )
#else
struct bwb_line *
bwb_write( l )
   struct bwb_line *l;
#endif
   {
   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, NUMBER );
      }

   /* 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
         return bwb_zline( l );
         }

      req_devnumber = (int) exp_getnval( 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
         return bwb_zline( l );
         }

      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

         return bwb_zline( l );
         }

      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

         return bwb_zline( l );
         }

#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 ) );
            prn_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_findnval( &nvar, nvar.array_pos ) =
               exp_getnval( e );
#if NUMBER_DOUBLE
            sprintf( tbuf, " %.*lf", prn_precision( &nvar ),
               var_getnval( &nvar ) );
#else
            sprintf( tbuf, " %.*f", prn_precision( &nvar ),
               var_getnval( &nvar ) );
#endif
            prn_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 */

   return bwb_zline( l );
   }

#endif

